diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000000..9c214fa0542 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,23 @@ +dev: + - changed-files: + - any-glob-to-any-file: [ '*', 'Utils/**', '/.github/**' ] + +engine: + - changed-files: + - any-glob-to-any-file: [ 'Mage/**' ] + +client: + - changed-files: + - any-glob-to-any-file: [ 'Mage.Client/**', 'Mage.Common/**', 'Mage.Plugins/**' ] + +server: + - changed-files: + - any-glob-to-any-file: [ 'Mage.Server*/**' ] + +tests: + - changed-files: + - any-glob-to-any-file: [ 'Mage.Verify/**', 'Mage.Tests/**', 'Mage.Reports/**' ] + +cards: + - changed-files: + - any-glob-to-any-file: [ 'Mage.Sets/**' ] \ No newline at end of file diff --git a/.github/workflows/labeler-auto.yml b/.github/workflows/labeler-auto.yml new file mode 100644 index 00000000000..2f88b117f98 --- /dev/null +++ b/.github/workflows/labeler-auto.yml @@ -0,0 +1,15 @@ +name: "Pull Request Labeler (auto)" +on: + - pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: label-the-PR + uses: actions/labeler@v5 + with: + configuration-path: '.github/labeler.yml' \ No newline at end of file diff --git a/.github/workflows/labeler-manual.yml b/.github/workflows/labeler-manual.yml new file mode 100644 index 00000000000..846436ad507 --- /dev/null +++ b/.github/workflows/labeler-manual.yml @@ -0,0 +1,24 @@ +name: "Pull Request Labeler (manual)" +on: + workflow_dispatch: + inputs: + oldPRs: + # no multi lines support, so call by single PR only + description: 'PR number to process' + required: true + type: string + default: '123' + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - id: label-the-PR + uses: actions/labeler@v5 + with: + configuration-path: '.github/labeler.yml' + pr-number: | + ${{ github.event.inputs.oldPRs }} \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 7e7b9a4b575..6fb4a8aa0d6 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -1517,7 +1517,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { public void setConnectButtonText(String status) { this.btnConnect.setText(status); - changeGUISize(); // Needed to layout the tooltbar after text length change + + // Needed to layout the tooltbar after text length change + // TODO: need research, is it actual? + GUISizeHelper.refreshGUIAndCards(); + this.btnConnect.repaint(); this.btnConnect.revalidate(); } @@ -1741,8 +1745,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } } - public void changeGUISize() { - ImageCaches.flush(); + /** + * Refresh whole GUI including cards and card images. + * Use it after new images downloaded, new fonts or theme settings selected. + */ + public void refreshGUIAndCards() { + ImageCaches.clearAll(); + setGUISize(); setGUISizeTooltipContainer(); diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index 40fb19b0e9f..5b59987d83d 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -647,8 +647,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private boolean separateCreatures = true; public enum Role { - MAINDECK("Maindeck"), - SIDEBOARD("Sideboard"); + MAINDECK("Main deck"), + SIDEBOARD("Sideboard/commander"); Role(String name) { this.name = name; @@ -1705,7 +1705,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg java.util.List cardPool = CardRepository.instance.findCards(cardCriteria); if (!cardPool.isEmpty()) { - Card acard = cardPool.get(RandomUtil.nextInt(cardPool.size())).getMockCard(); + Card acard = cardPool.get(RandomUtil.nextInt(cardPool.size())).createMockCard(); if (acard.getName().equals(card.getName())) { CardView pimpedCard = new CardView(acard); @@ -1751,7 +1751,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg for (CardView card : stack) { CardInfo oldestCardInfo = CardRepository.instance.findOldestNonPromoVersionCard(card.getName()); if (oldestCardInfo != null) { - CardView oldestCardView = new CardView(oldestCardInfo.getMockCard()); + CardView oldestCardView = new CardView(oldestCardInfo.createMockCard()); this.removeCardView(card); eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD); this.addCardView(oldestCardView, false); @@ -1955,21 +1955,21 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private void showCardRightClickMenu(@SuppressWarnings("unused") final CardView card, MouseEvent e) { JPopupMenu menu = new JPopupMenu(); - JMenuItem hide = new JMenuItem("Hide"); + JMenuItem hide = new JMenuItem("Hide (hidden in sideboard)"); hide.addActionListener(e2 -> hideSelection()); menu.add(hide); - JMenuItem invertSelection = new JMenuItem("Invert Selection"); + JMenuItem invertSelection = new JMenuItem("Invert selection"); invertSelection.addActionListener(e2 -> invertSelection()); menu.add(invertSelection); - JMenuItem chooseMatching = new JMenuItem("Choose Matching"); + JMenuItem chooseMatching = new JMenuItem("Select same cards"); chooseMatching.addActionListener(e2 -> chooseMatching()); menu.add(chooseMatching); // Show 'Duplicate Selection' for FREE_BUILDING if (this.mode == Constants.DeckEditorMode.FREE_BUILDING) { - JMenuItem duplicateSelection = new JMenuItem("Duplicate Selection"); + JMenuItem duplicateSelection = new JMenuItem("Duplicate selected cards"); duplicateSelection.addActionListener(e2 -> duplicateSelection()); menu.add(duplicateSelection); } diff --git a/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java index a537320007d..4ae4af5fcf4 100644 --- a/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java +++ b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java @@ -64,7 +64,7 @@ public class VirtualCardInfo { return; } - this.init(new CardView(cardInfo.getCard()), bigCard, gameId); + this.init(new CardView(cardInfo.createCard()), bigCard, gameId); } public void init(CardView cardView, BigCard bigCard, UUID gameId) { diff --git a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java index c10c66403a0..474bbdee84e 100644 --- a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java @@ -126,7 +126,7 @@ public class MageEditorPane extends JEditorPane { if (cardView == null) { CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null); if (card != null) { - cardView = new CardView(card.getMockCard()); + cardView = new CardView(card.createMockCard()); } } diff --git a/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java b/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java index cb99590c42a..0953c2518e2 100644 --- a/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java +++ b/Mage.Client/src/main/java/mage/client/components/MageRoundPane.java @@ -24,12 +24,12 @@ public class MageRoundPane extends JPanel { private int Y_OFFSET = 30; private final Color defaultBackgroundColor = new Color(141, 130, 112, 200); // color of the frame of the popup window private Color backgroundColor = defaultBackgroundColor; - private static final SoftValuesLoadingCache SHADOW_IMAGE_CACHE; - private static final SoftValuesLoadingCache IMAGE_CACHE; + private static final SoftValuesLoadingCache ROUND_PANEL_SHADOW_IMAGES_CACHE; + private static final SoftValuesLoadingCache ROUND_PANEL_IMAGES_CACHE; static { - SHADOW_IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createShadowImage)); - IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createImage)); + ROUND_PANEL_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createImage)); + ROUND_PANEL_SHADOW_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createShadowImage)); } private static final class ShadowKey { @@ -132,7 +132,7 @@ public class MageRoundPane extends JPanel { @Override protected void paintComponent(Graphics g) { - g.drawImage(IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null); + g.drawImage(ROUND_PANEL_IMAGES_CACHE.getOrThrow(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null); } private static BufferedImage createImage(Key key) { @@ -146,7 +146,7 @@ public class MageRoundPane extends JPanel { Graphics2D g2 = image.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - BufferedImage shadow = SHADOW_IMAGE_CACHE.getOrThrow(new ShadowKey(w, h)); + BufferedImage shadow = ROUND_PANEL_SHADOW_IMAGES_CACHE.getOrThrow(new ShadowKey(w, h)); { int xOffset = (shadow.getWidth() - w) / 2; diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java index a9f99c1daa8..f1644cb1b86 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java @@ -4,16 +4,13 @@ import mage.cards.Card; import mage.cards.decks.Deck; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; import mage.cards.repository.ExpansionRepository; import mage.client.dialog.PreferencesDialog; import mage.client.util.sets.ConstructedFormats; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; -import mage.constants.Rarity; import mage.constants.SuperType; import mage.util.RandomUtil; -import mage.util.TournamentUtil; import java.util.*; @@ -40,17 +37,16 @@ public final class DeckGenerator { /** * Builds a deck out of the selected block/set/format. * - * @return a path to the generated deck. + * @return a path to the generated deck or null on canceled */ public static String generateDeck() { - genDialog = new DeckGeneratorDialog(); if (genDialog.getSelectedColors() != null) { Deck deck = buildDeck(); return genDialog.saveDeck(deck); + } else { + return null; } - // If the deck couldn't be generated or the user cancelled, repopulate the deck selection with its cached value - return PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE, null); } protected static Deck buildDeck() { @@ -230,6 +226,6 @@ public final class DeckGenerator { private static Card getBasicLand(ColoredManaSymbol color, Map> basicLands) { String landName = DeckGeneratorPool.getBasicLandName(color.toString()); List basicLandsInfo = basicLands.get(landName); - return basicLandsInfo.get(RandomUtil.nextInt(basicLandsInfo.size())).getMockCard().copy(); + return basicLandsInfo.get(RandomUtil.nextInt(basicLandsInfo.size())).createMockCard().copy(); } } diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index 11778ac2697..4f27cc4f1a2 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -92,7 +92,7 @@ public class DeckGeneratorDialog { c.weightx = 0.80; mainPanel.add(setPanel, c); - cbSets = new JComboBox<>(ConstructedFormats.getTypes()); + cbSets = new JComboBox<>(ConstructedFormats.getTypes(false).toArray()); cbSets.setSelectedIndex(0); cbSets.setAlignmentX(0.0F); setPanel.add(cbSets); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java index e2b9d5cf6c1..5c60b989043 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java @@ -278,7 +278,7 @@ public class DeckGeneratorPool public List filterLands(List landCardsInfo) { List matchingLandList = new ArrayList<>(); for(CardInfo landCardInfo: landCardsInfo) { - Card landCard = landCardInfo.getMockCard(); + Card landCard = landCardInfo.createMockCard(); if(landProducesChosenColors(landCard)) { matchingLandList.add(landCard); } @@ -556,7 +556,7 @@ public class DeckGeneratorPool if (retrievedCount > 0 && retrievedCount >= spellCount) { int tries = 0; while (count < spellCount) { - Card card = cardPool.get(RandomUtil.nextInt(retrievedCount)).getMockCard(); + Card card = cardPool.get(RandomUtil.nextInt(retrievedCount)).createMockCard(); if (genPool.isValidSpellCard(card)) { int cardCMC = card.getManaValue(); for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) { diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 4f820a1fedd..9bd926665e5 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -76,7 +76,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // prepare search dialog with checkboxes listCodeSelected = new CheckBoxList(); List checkboxes = new ArrayList<>(); - for (String item : ConstructedFormats.getTypes()) { + for (String item : ConstructedFormats.getTypes(false)) { if (!item.equals(ConstructedFormats.ALL_SETS)) { checkboxes.add(item); } @@ -480,7 +480,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } } // filter by settings - Card card = cardInfo.getMockCard(); + Card card = cardInfo.createMockCard(); if (!filter.match(card, null)) { continue; } @@ -540,7 +540,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } private void reloadSetsCombobox() { - DefaultComboBoxModel model = new DefaultComboBoxModel<>(ConstructedFormats.getTypes()); + DefaultComboBoxModel model = new DefaultComboBoxModel<>(ConstructedFormats.getTypes(false).toArray()); cbExpansionSet.setModel(model); } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java index a62ae20b4ea..a1f34640c58 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java @@ -80,6 +80,7 @@ public class DeckArea extends javax.swing.JPanel { deckList.setRole(DragCardGrid.Role.MAINDECK); sideboardList.setRole(DragCardGrid.Role.SIDEBOARD); + // card actions in main // When a selection happens in one pane, deselect the selection in the other deckList.addDragCardGridListener(new DragCardGrid.DragCardGridListener() { @Override @@ -121,6 +122,8 @@ public class DeckArea extends javax.swing.JPanel { } } }); + + // card actions in sideboard sideboardList.addDragCardGridListener(new DragCardGrid.DragCardGridListener() { @Override public void cardsSelected() { diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form index 98a309ce94f..df74cb92578 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form @@ -191,7 +191,7 @@ - + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 580537ccaa0..764d25ef3f5 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -290,11 +290,10 @@ public class DeckEditorPanel extends javax.swing.JPanel { private Card retrieveTemporaryCard(SimpleCardView cardView) { Card card = temporaryCards.get(cardView.getId()); if (card == null) { - // Need to make a new card - Logger.getLogger(DeckEditorPanel.class).info("Retrieve " + cardView.getCardNumber() + " Failed"); - card = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()).getCard(); + // need to make a new card (example: on duplicate, on show hidden cards) + card = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()).createCard(); } else { - // Only need a temporary card once + // restore temp card (example: after drag to new zone) temporaryCards.remove(cardView.getId()); } return card; @@ -578,7 +577,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { // add cards CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()); for (int i = cardsFound; i < numberToSet; i++) { - cards.add(cardInfo.getMockCard()); + cards.add(cardInfo.createMockCard()); } } else { // remove cards @@ -605,7 +604,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } else { // editor: create mock card CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()); - card = cardInfo != null ? cardInfo.getMockCard() : null; + card = cardInfo != null ? cardInfo.createMockCard() : null; } if (card != null) { @@ -633,7 +632,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { SimpleCardView cardView = (SimpleCardView) event.getSource(); CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()); - Card card = cardInfo != null ? cardInfo.getMockCard() : null; + Card card = cardInfo != null ? cardInfo.createMockCard() : null; if (card != null) { deck.getSideboard().add(card); } @@ -1014,7 +1013,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { }); btnGenDeck.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/card_panel.png"))); // NOI18N - btnGenDeck.setText("Random"); + btnGenDeck.setText("Generate"); btnGenDeck.setIconTextGap(1); btnGenDeck.setName("btnGenDeck"); // NOI18N btnGenDeck.addActionListener(new java.awt.event.ActionListener() { @@ -1470,6 +1469,9 @@ public class DeckEditorPanel extends javax.swing.JPanel { try { MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); String path = DeckGenerator.generateDeck(); + if (path == null) { + return; + } deck = Deck.load(DeckImporter.importDeckFromFile(path, false), true, true); } catch (GameException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading generated deck", JOptionPane.ERROR_MESSAGE); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/CollectionViewerPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/CollectionViewerPanel.java index 07706b5d756..9f2bfb8e0c9 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/CollectionViewerPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/CollectionViewerPanel.java @@ -44,7 +44,7 @@ public final class CollectionViewerPanel extends JPanel { } private void reloadFormatCombobox() { - DefaultComboBoxModel model = new DefaultComboBoxModel<>(ConstructedFormats.getTypes()); + DefaultComboBoxModel model = new DefaultComboBoxModel<>(ConstructedFormats.getTypes(true).toArray()); formats.setModel(model); formats.setSelectedItem(ConstructedFormats.getDefault()); } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java index 830f0506cc0..65bb80dc629 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java @@ -19,6 +19,7 @@ import mage.client.util.audio.AudioManager; import mage.client.util.sets.ConstructedFormats; import mage.components.ImagePanel; import mage.components.ImagePanelStyle; +import mage.constants.MageObjectType; import mage.game.command.Dungeon; import mage.game.command.Emblem; import mage.game.command.Plane; @@ -26,6 +27,7 @@ import mage.cards.RateCard; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; +import mage.game.permanent.token.custom.XmageToken; import mage.view.*; import org.apache.log4j.Logger; import org.mage.card.arcane.ManaSymbols; @@ -230,13 +232,22 @@ public class MageBook extends JComponent { public List loadTokens() { List res = new ArrayList<>(); - // tokens - List allTokens = TokenRepository.instance.getByType(TokenType.TOKEN) - .stream() - .filter(token -> token.getSetCode().equals(currentSet)) - .collect(Collectors.toList()); + // tokens (official and xmage's inner) + List allTokens = new ArrayList<>(TokenRepository.instance.getByType(TokenType.TOKEN)); + allTokens.addAll(TokenRepository.instance.getByType(TokenType.XMAGE)); + allTokens.removeIf(token -> !token.getSetCode().equals(currentSet)); allTokens.forEach(token -> { - TokenImpl newToken = TokenImpl.createTokenByClassName(token.getFullClassFileName()); + TokenImpl newToken; + switch (token.getTokenType()) { + case XMAGE: + newToken = new XmageToken(token.getName()); + break; + + case TOKEN: + default: + newToken = TokenImpl.createTokenByClassName(token.getFullClassFileName()); + break; + } if (newToken != null) { newToken.setExpansionSetCode(currentSet); newToken.setImageNumber(token.getImageNumber()); @@ -337,9 +348,8 @@ public class MageBook extends JComponent { addItem(needItems.get(i), rectangle); rectangle = CardPosition.translatePosition(i - conf.CARDS_PER_PAGE / 2, rectangle, conf); } - - jLayeredPane.repaint(); } + jLayeredPane.repaint(); } private void addItem(Object item, Rectangle position) { @@ -387,7 +397,7 @@ public class MageBook extends JComponent { draftRating.setBounds(rectangle.x, rectangle.y + cardImg.getCardLocation().getCardHeight() + dy, cardDimensions.getFrameWidth(), 20); draftRating.setHorizontalAlignment(SwingConstants.CENTER); draftRating.setFont(jLayeredPane.getFont().deriveFont(jLayeredPane.getFont().getStyle() | Font.BOLD)); - if (card.isOriginalACard()) { + if (card.getMageObjectType().equals(MageObjectType.CARD)) { // card draftRating.setText("draft rating: " + RateCard.rateCard(card, null)); } else { @@ -402,14 +412,14 @@ public class MageBook extends JComponent { if (cardDimension == null) { cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), ClientDefaultSettings.dimensions.getFrameHeight()); } - PermanentToken newToken = new PermanentToken(token, null, null); - newToken.removeSummoningSickness(); - PermanentView theToken = new PermanentView(newToken, null, null, null); - theToken.setInViewerOnly(true); - final MageCard cardImg = Plugins.instance.getMagePermanent(theToken, bigCard, new CardIconRenderSettings(), cardDimension, gameId, true, PreferencesDialog.getRenderMode(), true); + PermanentToken fakePermanent = new PermanentToken(token, UUID.randomUUID(), null); + fakePermanent.removeSummoningSickness(); + PermanentView permanentView = new PermanentView(fakePermanent, null, null, null); + permanentView.setInViewerOnly(true); + final MageCard cardImg = Plugins.instance.getMagePermanent(permanentView, bigCard, new CardIconRenderSettings(), cardDimension, gameId, true, PreferencesDialog.getRenderMode(), true); cardImg.setCardContainerRef(jLayeredPane); jLayeredPane.add(cardImg, JLayeredPane.DEFAULT_LAYER, 10); - cardImg.update(theToken); + cardImg.update(permanentView); cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimensions.getFrameWidth(), cardDimensions.getFrameHeight()); } @@ -434,7 +444,7 @@ public class MageBook extends JComponent { List cards = CardRepository.instance.findCards(criteria); cards.sort(new NaturalOrderCardNumberComparator()); List res = new ArrayList<>(); - cards.forEach(card -> res.add(new CardView(card.getMockCard()))); + cards.forEach(card -> res.add(new CardView(card.createMockCard()))); return res; } @@ -457,15 +467,22 @@ public class MageBook extends JComponent { private void updateCardStats(String setCode, boolean isCardsShow) { // sets do not have total cards number, it's a workaround + // inner set + if (setCode.equals(TokenRepository.XMAGE_TOKENS_SET_CODE)) { + setCaption.setText("Inner Xmage images"); + setInfo.setText(""); + return; + }; + + // normal set ExpansionSet set = Sets.findSet(setCode); - if (set != null) { - setCaption.setText(set.getCode() + " - " + set.getName()); - } else { + if (set == null) { setCaption.setText("ERROR"); setInfo.setText("ERROR"); return; } + setCaption.setText(set.getCode() + " - " + set.getName()); if (!isCardsShow) { // tokens or emblems, stats not need setInfo.setText(""); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/table/MageCardComparator.java b/Mage.Client/src/main/java/mage/client/deckeditor/table/MageCardComparator.java index 51542210539..a08755b1834 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/table/MageCardComparator.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/MageCardComparator.java @@ -87,8 +87,8 @@ public class MageCardComparator implements CardViewComparator { bCom = RateCard.rateCard(b, null); break; case 10: - aCom = a.getColorIdentityStr(); - bCom = b.getColorIdentityStr(); + aCom = a.getOriginalColorIdentity(); + bCom = b.getOriginalColorIdentity(); break; default: break; diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/table/TableModel.java b/Mage.Client/src/main/java/mage/client/deckeditor/table/TableModel.java index 8650542b7d2..f4406471348 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/table/TableModel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/TableModel.java @@ -255,7 +255,7 @@ public class TableModel extends AbstractTableModel implements ICardGrid { case 9: return RateCard.rateCard(c, null); case 10: - return ManaSymbols.getClearManaCost(c.getColorIdentityStr()); + return ManaSymbols.getClearManaCost(c.getOriginalColorIdentity()); default: return "error"; } diff --git a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java index ca47f570d56..32f7e2f49e4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java @@ -173,7 +173,7 @@ public class AddLandDialog extends MageDialog { int foundLands = 0; int foundNoneAfter = 0; for (int i = 0; foundLands != number && foundNoneAfter < 1000; i++) { - Card land = cards.get(RandomUtil.nextInt(cards.size())).getMockCard(); + Card land = cards.get(RandomUtil.nextInt(cards.size())).createMockCard(); boolean useLand = !useFullArt; if (useFullArt && (land.getFrameStyle() == FrameStyle.BFZ_FULL_ART_BASIC || land.getFrameStyle() == FrameStyle.UGL_FULL_ART_BASIC diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index f5deb4e6926..3c416cf24b1 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -2928,8 +2928,8 @@ public class PreferencesDialog extends javax.swing.JDialog { save(prefs, dialog.sliderCardSizeMinBattlefield, KEY_GUI_CARD_BATTLEFIELD_MIN_SIZE, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.sliderCardSizeMaxBattlefield, KEY_GUI_CARD_BATTLEFIELD_MAX_SIZE, "true", "false", UPDATE_CACHE_POLICY); - // do as worker job - GUISizeHelper.changeGUISize(); + // refresh full GUI with new settings + GUISizeHelper.refreshGUIAndCards(); } private void exitButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitButtonActionPerformed diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index 7bf737f1946..0af62a61e41 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -2,6 +2,7 @@ package mage.client.dialog; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.icon.*; import mage.abilities.keyword.TransformAbility; import mage.cards.*; @@ -184,6 +185,18 @@ public class TestCardRenderDialog extends MageDialog { if (perm.isTransformable()) { perm.setTransformed(true); } + + // workaround to apply face down image and other settings + if (perm.isFaceDown(game)) { + BecomesFaceDownCreatureEffect.makeFaceDownObject( + game, + null, + perm, + BecomesFaceDownCreatureEffect.findFaceDownType(game, perm), + null + ); + } + PermanentView cardView = new PermanentView(perm, permCard, controllerId, game); cardView.setInViewerOnly(false); // must false for face down return cardView; @@ -386,9 +399,9 @@ public class TestCardRenderDialog extends MageDialog { List cardViews = new ArrayList<>(); - /* test morphed - cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "263", 0, 0, 0, false, null)); // mountain - cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, null)); // Judith, the Scourge Diva + //* test face down + cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "263", 0, 0, 0, false, false, null)); // mountain + 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(), "ELD", "38")); // Animating Faerie (adventure card) cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down @@ -408,9 +421,35 @@ public class TestCardRenderDialog extends MageDialog { /* test split, transform and mdf in hands cardViews.add(createHandCard(game, playerYou.getId(), "SOI", "97")); // Accursed Witch - cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice - cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer cardViews.add(createHandCard(game, playerYou.getId(), "ZNR", "134")); // Akoum Warrior + cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice + cardViews.add(createHandCard(game, playerYou.getId(), "DGM", "123")); // Beck // Call + cardViews.add(createHandCard(game, playerYou.getId(), "AKH", "210")); // Dusk // Dawn + //*/ + + /* test adventure cards in hands + cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer + cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "222")); // Cruel Somnophage + cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "227")); // Gingerbread Hunter + cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "221")); // Callous Sell-Sword + cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "149")); // Beanstalk Giant + cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "220")); // Beluna Grandsquall + //*/ + + /* test saga and case cards in hands + cardViews.add(createHandCard(game, playerYou.getId(), "DOM", "90")); // The Eldest Reborn + cardViews.add(createHandCard(game, playerYou.getId(), "MH2", "259")); // Urza's Saga + cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "113")); // Case of the Burning Masks + cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse + //*/ + + /* test case, class and saga cards in hands + cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "113")); // Case of the Burning Masks + cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse + cardViews.add(createHandCard(game, playerYou.getId(), "AFR", "6")); // Cleric Class + cardViews.add(createHandCard(game, playerYou.getId(), "AFR", "230")); // Rogue Class + cardViews.add(createHandCard(game, playerYou.getId(), "DOM", "90")); // The Eldest Reborn + cardViews.add(createHandCard(game, playerYou.getId(), "MH2", "259")); // Urza's Saga //*/ /* test meld cards in hands and battlefield @@ -441,7 +480,7 @@ public class TestCardRenderDialog extends MageDialog { //cardViews.add(createPermanentCard(game, playerYou.getId(), "KHM", "50", 1, 1, 0, true, false, additionalIcons)); // Cosima, God of the Voyage //*/ - //* test tokens + /* test tokens // normal cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "10E", 0, false, false)); cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "XXX", 1, false, false)); @@ -478,7 +517,7 @@ public class TestCardRenderDialog extends MageDialog { PermanentView oldPermanent = (PermanentView) main.getGameCard(); PermanentView newPermament = new PermanentView( oldPermanent, - game.getCard(oldPermanent.getOriginalId()), + game.getCard(oldPermanent.getId()), UUID.randomUUID(), game ); @@ -826,7 +865,7 @@ public class TestCardRenderDialog extends MageDialog { }//GEN-LAST:event_comboRenderModeItemStateChanged private void sliderSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sliderSizeStateChanged - // from DragCardGrid + // from DragCardGrid // Fraction in [-1, 1] float sliderFrac = ((float) (sliderSize.getValue() - 50)) / 50; // Convert to frac in [0.5, 2.0] exponentially diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index 729ea7b1755..f4103cd8554 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -117,7 +117,7 @@ public class MageActionCallback implements ActionCallback { } @Override - public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) { + public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) { // send mouse clicked event to the card's area and other cards list components for processing if (e.isConsumed()) { return; @@ -720,7 +720,7 @@ public class MageActionCallback implements ActionCallback { switch (enlargeMode) { case COPY: if (cardView instanceof PermanentView) { - image = ImageCache.getImageOriginal(((PermanentView) cardView).getOriginal()).getImage(); + image = ImageCache.getCardImageOriginal(((PermanentView) cardView).getOriginal()).getImage(); } break; case ALTERNATE: @@ -729,10 +729,14 @@ public class MageActionCallback implements ActionCallback { && !cardView.isFlipCard() && !cardView.canTransform() && ((PermanentView) cardView).isCopy()) { - image = ImageCache.getImageOriginal(((PermanentView) cardView).getOriginal()).getImage(); + image = ImageCache.getCardImageOriginal(((PermanentView) cardView).getOriginal()).getImage(); } else { - image = ImageCache.getImageOriginalAlternateName(cardView).getImage(); + image = ImageCache.getCardImageAlternate(cardView).getImage(); displayCard = displayCard.getSecondCardFace(); + if (displayCard == null) { + // opponent's face down cards are hidden, so no alternative + displayCard = cardPanel.getOriginal(); + } } } break; @@ -745,7 +749,6 @@ public class MageActionCallback implements ActionCallback { } else { logger.warn("No Card preview Pane in Mage Frame defined. Card: " + cardView.getName()); } - } catch (Exception e) { logger.warn("Problem dring display of enlarged card", e); } @@ -786,6 +789,7 @@ public class MageActionCallback implements ActionCallback { private void displayCardInfo(CardView card, Image image, BigCard bigCard) { if (image instanceof BufferedImage) { + // IMAGE MODE // XXX: scaled to fit width bigCard.setCard(card.getId(), enlargeMode, image, card.getRules(), card.isToRotate()); // if it's an ability, show only the ability text as overlay @@ -795,6 +799,7 @@ public class MageActionCallback implements ActionCallback { bigCard.hideTextComponent(); } } else { + // TEXT MODE JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight()); panel.setVisible(true); bigCard.hideTextComponent(); diff --git a/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java b/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java index dfa7f117b1f..82977aa47df 100644 --- a/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java @@ -58,10 +58,11 @@ public class NewPlayerPanel extends javax.swing.JPanel { protected void generateDeck() { String path = DeckGenerator.generateDeck(); - if (path != null) { - this.txtPlayerDeck.setText(path); - MageFrame.getPreferences().put("defaultDeckPath", path); + if (path == null) { + return; } + this.txtPlayerDeck.setText(path); + MageFrame.getPreferences().put("defaultDeckPath", path); } public String getPlayerName() { diff --git a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java index c5e836f9e26..4014c0c2a04 100644 --- a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java +++ b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java @@ -2,8 +2,9 @@ package mage.client.themes; import mage.abilities.hint.HintUtils; import mage.abilities.icon.CardIconColor; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageCaches; import org.mage.card.arcane.SvgUtils; -import org.mage.plugins.card.images.ImageCache; import java.awt.*; @@ -350,6 +351,6 @@ public enum ThemeType { } // reload card icons and other rendering things from cache - it can depend on current theme - ImageCache.clearCache(); + GUISizeHelper.refreshGUIAndCards(); } } \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java b/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java index bc2a1b4654d..410f9d39bfe 100644 --- a/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Map; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public final class CardsViewUtil { @@ -18,7 +18,7 @@ public final class CardsViewUtil { for (SimpleCardView simple : view.values()) { CardInfo cardInfo = CardRepository.instance.findCard(simple.getExpansionSetCode(), simple.getCardNumber()); - Card card = cardInfo != null ? cardInfo.getMockCard() : null; + Card card = cardInfo != null ? cardInfo.createMockCard() : null; if (card != null) { cards.put(simple.getId(), new CardView(card, simple)); } @@ -35,7 +35,7 @@ public final class CardsViewUtil { Card card = loadedCards.get(key); if (card == null) { CardInfo cardInfo = CardRepository.instance.findCard(simple.getExpansionSetCode(), simple.getCardNumber()); - card = cardInfo != null ? cardInfo.getMockCard() : null; + card = cardInfo != null ? cardInfo.createMockCard() : null; loadedCards.put(key, card); } if (card != null) { diff --git a/Mage.Client/src/main/java/mage/client/util/DeckUtil.java b/Mage.Client/src/main/java/mage/client/util/DeckUtil.java index 45cf31101a3..b171fcd06c3 100644 --- a/Mage.Client/src/main/java/mage/client/util/DeckUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/DeckUtil.java @@ -27,7 +27,7 @@ public final class DeckUtil { deck.setName(view.getName()); for (SimpleCardView cardView : view.getCards().values()) { CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()); - Card card = cardInfo != null ? cardInfo.getMockCard() : null; + Card card = cardInfo != null ? cardInfo.createMockCard() : null; if (card != null) { deck.getCards().add(card); } else { @@ -36,7 +36,7 @@ public final class DeckUtil { } for (SimpleCardView cardView : view.getSideboard().values()) { CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()); - Card card = cardInfo != null ? cardInfo.getMockCard() : null; + Card card = cardInfo != null ? cardInfo.createMockCard() : null; if (card != null) { deck.getSideboard().add(card); } else { diff --git a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java index 0e1dec00901..c01006ee9d1 100644 --- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java +++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java @@ -84,9 +84,11 @@ public final class GUISizeHelper { return new Font("Arial", Font.PLAIN, 14); } - public static void changeGUISize() { + public static void refreshGUIAndCards() { calculateGUISizes(); - MageFrame.getInstance().changeGUISize(); + if (MageFrame.getInstance() != null) { + MageFrame.getInstance().refreshGUIAndCards(); + } } public static void calculateGUISizes() { diff --git a/Mage.Client/src/main/java/mage/client/util/ImageCaches.java b/Mage.Client/src/main/java/mage/client/util/ImageCaches.java index 5850293bad9..91b1798bbaa 100644 --- a/Mage.Client/src/main/java/mage/client/util/ImageCaches.java +++ b/Mage.Client/src/main/java/mage/client/util/ImageCaches.java @@ -1,11 +1,11 @@ - package mage.client.util; -import java.util.ArrayList; - import com.google.common.cache.Cache; +import java.util.ArrayList; + /** + * GUI: collect info about all used image caches, so it can be cleared from a single place * * @author draxdyn */ @@ -22,7 +22,11 @@ public final class ImageCaches { return map; } - public static void flush() { + /** + * Global method to clear all images cache. + * Warning, GUI must be refreshed too for card updates, so use GUISizeHelper.refreshGUIAndCards instead + */ + public static void clearAll() { for (Cache map : IMAGE_CACHES) { map.invalidateAll(); } diff --git a/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java b/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java index 250e4ac0298..ef206c05808 100644 --- a/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java +++ b/Mage.Client/src/main/java/mage/client/util/TransformedImageCache.java @@ -55,11 +55,10 @@ public final class TransformedImageCache { } } - private static final SoftValuesLoadingCache> IMAGE_CACHE; + private static final SoftValuesLoadingCache> TRANSFORMED_IMAGES_CACHE; static { - // TODO: can we use a single map? - IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(TransformedImageCache::createTransformedImageCache)); + TRANSFORMED_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(TransformedImageCache::createTransformedImageCache)); } private static SoftValuesLoadingCache createTransformedImageCache(Key key) { @@ -139,6 +138,6 @@ public final class TransformedImageCache { if (resHeight < 3) { resHeight = 3; } - return IMAGE_CACHE.getOrThrow(new Key(resWidth, resHeight, angle)).getOrThrow(image); + return TRANSFORMED_IMAGES_CACHE.getOrThrow(new Key(resWidth, resHeight, angle)).getOrThrow(image); } } diff --git a/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java b/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java index 9d4baee3dee..850ede0600b 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/GuiDisplayUtil.java @@ -9,9 +9,11 @@ import mage.constants.*; import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; +import net.java.truevfs.access.TFile; import org.jdesktop.swingx.JXPanel; import org.mage.card.arcane.ManaSymbols; import org.mage.card.arcane.UI; +import org.mage.plugins.card.utils.CardImageUtils; import javax.swing.*; import java.awt.*; @@ -402,6 +404,15 @@ public final class GuiDisplayUtil { buffer.append("

Card Zone: ").append(zone).append("

"); } + // missing image info in card popup + boolean displayFullImagePath = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_FULL_IMAGE_PATH, "false").equals("true"); + if (displayFullImagePath) { + String imageFile = CardImageUtils.buildImagePathToCardView(card); + if (imageFile.startsWith("ERROR") || !(new TFile(imageFile).exists())) { + buffer.append("

Missing image: ").append(imageFile).append("

"); + } + } + buffer.append("
"); return buffer; } diff --git a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java index 3898135dc6e..db958f3148f 100644 --- a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java +++ b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java @@ -3,6 +3,7 @@ package mage.client.util.sets; import mage.cards.repository.ExpansionInfo; import mage.cards.repository.ExpansionRepository; import mage.cards.repository.RepositoryEvent; +import mage.cards.repository.TokenRepository; import mage.constants.SetType; import mage.deck.Standard; import mage.game.events.Listener; @@ -27,6 +28,7 @@ public final class ConstructedFormats { public static final String HISTORIC = "- Historic"; public static final String JOKE = "- Joke Sets"; public static final String CUSTOM = "- Custom"; + public static final String XMAGE_SETS = "- XMAGE"; // inner sets like XMAGE (special tokens) public static final Standard STANDARD_CARDS = new Standard(); // Attention -Month is 0 Based so Feb = 1 for example. // @@ -62,8 +64,15 @@ public final class ConstructedFormats { private ConstructedFormats() { } - public static String[] getTypes() { - return formats.toArray(new String[0]); + /** + * @param includeInnerSets add XMAGE set with inner cards/tokens like morph + */ + public static List getTypes(boolean includeInnerSets) { + List res = new ArrayList<>(formats); + if (!includeInnerSets) { + res.removeIf(s -> s.equals(XMAGE_SETS)); + } + return res; } public static String getDefault() { @@ -95,6 +104,7 @@ public final class ConstructedFormats { underlyingSetCodesPerFormat.put(HISTORIC, new ArrayList<>()); underlyingSetCodesPerFormat.put(JOKE, new ArrayList<>()); underlyingSetCodesPerFormat.put(CUSTOM, new ArrayList<>()); + underlyingSetCodesPerFormat.put(XMAGE_SETS, new ArrayList<>()); final Map expansionInfo = new HashMap<>(); formats.clear(); // prevent NPE on sorting if this is not the first try @@ -104,6 +114,9 @@ public final class ConstructedFormats { return; } + // inner sets + underlyingSetCodesPerFormat.get(XMAGE_SETS).add(TokenRepository.XMAGE_TOKENS_SET_CODE); + // build formats list for deck validators for (ExpansionInfo set : ExpansionRepository.instance.getAll()) { expansionInfo.put(set.getName(), set); @@ -266,6 +279,7 @@ public final class ConstructedFormats { }); if (!formats.isEmpty()) { + formats.add(0, XMAGE_SETS); formats.add(0, CUSTOM); formats.add(0, JOKE); formats.add(0, HISTORIC); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 7860a8283ad..9b620bf72f5 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -377,7 +377,7 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) (g.create()); - // Deferr to subclasses + // Defer to subclasses paintCard(g2d); // Done, dispose of the context @@ -854,7 +854,7 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen // VIEW mode (user can change card side at any time by n/d button) this.guiTransformed = !this.guiTransformed; - if (dayNightButton != null) { // if transformbable card is copied, button can be null + if (dayNightButton != null) { // if transformable card is copied, button can be null BufferedImage image = this.isTransformed() ? ImageManagerImpl.instance.getNightImage() : ImageManagerImpl.instance.getDayImage(); dayNightButton.setIcon(new ImageIcon(image)); } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java index fc3c365a3d5..779d5a82a72 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeImage.java @@ -14,8 +14,6 @@ import mage.constants.SubType; import mage.util.DebugUtil; import mage.view.CardView; import mage.view.CounterView; -import mage.view.PermanentView; -import mage.view.StackAbilityView; import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.mage.plugins.card.images.ImageCache; import org.mage.plugins.card.images.ImageCacheData; @@ -37,7 +35,7 @@ public class CardPanelRenderModeImage extends CardPanel { private static final long serialVersionUID = -3272134219262184411L; - private static final SoftValuesLoadingCache IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage)); + private static final SoftValuesLoadingCache IMAGE_MODE_RENDERED_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage)); private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter @@ -472,11 +470,7 @@ public class CardPanelRenderModeImage extends CardPanel { @Override public Image getImage() { if (this.hasImage) { - if (getGameCard().isFaceDown()) { - return getFaceDownImage().getImage(); - } else { - return ImageCache.getImageOriginal(getGameCard()).getImage(); - } + return ImageCache.getCardImageOriginal(getGameCard()).getImage(); } return null; } @@ -492,7 +486,7 @@ public class CardPanelRenderModeImage extends CardPanel { // draw background (selected/chooseable/playable) MageCardLocation cardLocation = getCardLocation(); g2d.drawImage( - IMAGE_CACHE.getOrThrow( + IMAGE_MODE_RENDERED_CACHE.getOrThrow( new Key(getInsets(), cardLocation.getCardWidth(), cardLocation.getCardHeight(), cardLocation.getCardWidth(), cardLocation.getCardHeight(), @@ -640,14 +634,9 @@ public class CardPanelRenderModeImage extends CardPanel { Util.threadPool.submit(() -> { try { - final ImageCacheData data; - if (getGameCard().isFaceDown()) { - data = getFaceDownImage(); - } else { - data = ImageCache.getImage(getGameCard(), getCardWidth(), getCardHeight()); - } + ImageCacheData data = ImageCache.getCardImage(getGameCard(), getCardWidth(), getCardHeight()); - // show path on miss image + // save missing image if (data.getImage() == null) { setFullPath(data.getPath()); } @@ -665,21 +654,6 @@ public class CardPanelRenderModeImage extends CardPanel { }); } - private ImageCacheData getFaceDownImage() { - // TODO: add download default images - if (isPermanent() && getGameCard() instanceof PermanentView) { - if (((PermanentView) getGameCard()).isMorphed()) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getManifestImage(); - } - } else if (this.getGameCard() instanceof StackAbilityView) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getCardbackImage(); - } - } - private int getManaWidth(String manaCost, int symbolMarginX) { int width = 0; manaCost = manaCost.replace("\\", ""); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java index eecad84fd9b..2ceccde53cd 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderModeMTGO.java @@ -3,46 +3,40 @@ package org.mage.card.arcane; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import mage.cards.action.ActionCallback; +import mage.client.util.ImageCaches; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; -import mage.view.StackAbilityView; import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.mage.plugins.card.images.ImageCache; -import org.mage.plugins.card.images.ImageCacheData; import java.awt.*; import java.awt.image.BufferedImage; import java.util.UUID; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * Render mode: MTGO - * */ public class CardPanelRenderModeMTGO extends CardPanel { - // // https://www.mtg.onl/evolution-of-magic-token-card-frame-design/ - // Map of generated images - private static final Cache IMAGE_CACHE = CacheBuilder - .newBuilder() - .maximumSize(3000) - .expireAfterAccess(60, TimeUnit.MINUTES) - .softValues() - .build(); + private static final Cache MTGO_MODE_RENDERED_CACHE = ImageCaches.register( + CacheBuilder + .newBuilder() + .maximumSize(3000) + .expireAfterAccess(60, TimeUnit.MINUTES) + .softValues() + .build() + ); // The art image for the card, loaded in from the disk private BufferedImage artImage; - // The faceart image for the card, loaded in from the disk (based on artid from mtgo) - private BufferedImage faceArtImage; - // Factory to generate card appropriate views private final CardRendererFactory cardRendererFactory = new CardRendererFactory(); @@ -161,11 +155,7 @@ public class CardPanelRenderModeMTGO extends CardPanel { if (artImage == null) { return null; } - if (getGameCard().isFaceDown()) { - return getFaceDownImage().getImage(); - } else { - return ImageCache.getImageOriginal(getGameCard()).getImage(); - } + return ImageCache.getCardImageOriginal(getGameCard()).getImage(); } @Override @@ -173,16 +163,21 @@ public class CardPanelRenderModeMTGO extends CardPanel { // Render the card if we don't have an image ready to use if (cardImage == null) { // Try to get card image from cache based on our card characteristics - ImageKey key = new ImageKey(getGameCard(), artImage, - getCardWidth(), getCardHeight(), - isChoosable(), isSelected(), isTransformed()); + ImageKey key = new ImageKey( + getGameCard(), + artImage, + getCardWidth(), + getCardHeight(), + isChoosable(), + isSelected(), + isTransformed() + ); try { - cardImage = IMAGE_CACHE.get(key, this::renderCard); - } catch (ExecutionException e) { + cardImage = MTGO_MODE_RENDERED_CACHE.get(key, this::renderCard); + } catch (Exception e) { + // TODO: research and replace with logs, message and backface image throw new RuntimeException(e); } - - // No cached copy exists? Render one and cache it } // And draw the image we now have @@ -237,8 +232,6 @@ public class CardPanelRenderModeMTGO extends CardPanel { // Use the art image and current rendered image from the card artImage = impl.artImage; cardRenderer.setArtImage(artImage); - faceArtImage = impl.faceArtImage; - cardRenderer.setFaceArtImage(faceArtImage); cardImage = impl.cardImage; } } @@ -252,7 +245,6 @@ public class CardPanelRenderModeMTGO extends CardPanel { cardImage = null; cardRenderer = cardRendererFactory.create(getGameCard()); cardRenderer.setArtImage(artImage); - cardRenderer.setFaceArtImage(faceArtImage); // Repaint repaint(); @@ -264,7 +256,6 @@ public class CardPanelRenderModeMTGO extends CardPanel { artImage = null; cardImage = null; cardRenderer.setArtImage(null); - cardRenderer.setFaceArtImage(null); // Stop animation setTappedAngle(isTapped() ? CardPanel.TAPPED_ANGLE : 0); @@ -276,29 +267,18 @@ public class CardPanelRenderModeMTGO extends CardPanel { // See if the image is already loaded //artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight()); //this.cardRenderer.setArtImage(artImage); + // Submit a task to draw with the card art when it arrives if (artImage == null) { final int stamp = ++updateArtImageStamp; Util.threadPool.submit(() -> { try { final BufferedImage srcImage; - final BufferedImage faceArtSrcImage; - if (getGameCard().isFaceDown()) { - // Nothing to do - srcImage = null; - faceArtSrcImage = null; - } else { - srcImage = ImageCache.getImage(getGameCard(), getCardWidth(), getCardHeight()).getImage(); - faceArtSrcImage = ImageCache.getFaceImage(getGameCard(), getCardWidth(), getCardHeight()).getImage(); - } - + srcImage = ImageCache.getCardImage(getGameCard(), getCardWidth(), getCardHeight()).getImage(); UI.invokeLater(() -> { if (stamp == updateArtImageStamp) { artImage = srcImage; cardRenderer.setArtImage(srcImage); - faceArtImage = faceArtSrcImage; - cardRenderer.setFaceArtImage(faceArtSrcImage); - if (srcImage != null) { // Invalidate and repaint cardImage = null; @@ -317,21 +297,6 @@ public class CardPanelRenderModeMTGO extends CardPanel { return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed()); } - private ImageCacheData getFaceDownImage() { - // TODO: add download default images - if (isPermanent() && getGameCard() instanceof PermanentView) { - if (((PermanentView) getGameCard()).isMorphed()) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getManifestImage(); - } - } else if (this.getGameCard() instanceof StackAbilityView) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getCardbackImage(); - } - } - /** * Render the card to a new BufferedImage at it's current dimensions * diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index 8d8b521ccad..05ac037eeda 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -55,10 +55,7 @@ public abstract class CardRenderer { protected final CardView cardView; // The card image - protected BufferedImage artImage; - - // The face card image - protected BufferedImage faceArtImage; + protected BufferedImage artImage; // TODO: make sure it changed/reset on face down/up change /////////////////////////////////////////////////////////////////////////// // Common layout metrics between all cards @@ -106,8 +103,8 @@ public abstract class CardRenderer { protected int borderWidth; // The parsed text of the card - protected final ArrayList textboxRules = new ArrayList<>(); - protected final ArrayList textboxKeywords = new ArrayList<>(); + protected ArrayList textboxRules = new ArrayList<>(); + protected ArrayList textboxKeywords = new ArrayList<>(); // The Construtor // The constructor should prepare all of the things that it can @@ -206,7 +203,7 @@ public abstract class CardRenderer { } // The Draw Method - // The draw method takes the information caculated by the constructor + // The draw method takes the information calculated by the constructor // and uses it to draw to a concrete size of card and graphics. public void draw(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) { @@ -240,7 +237,7 @@ public abstract class CardRenderer { // Template methods that are possible to override, but unlikely to be // overridden. // Draw the card back - protected void drawCardBack(Graphics2D g) { + protected void drawCardBackTexture(Graphics2D g) { g.setPaint(BG_TEXTURE_CARDBACK); g.fillRect(borderWidth, borderWidth, cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth); @@ -313,51 +310,6 @@ public abstract class CardRenderer { } private boolean lessOpaqueRulesTextBox = false; - protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, int alternate_h, Rectangle2D artRect, boolean shouldPreserveAspect) { - // Perform a process to make sure that the art is scaled uniformly to fill the frame, cutting - // off the minimum amount necessary to make it completely fill the frame without "squashing" it. - double fullCardImgWidth = faceArtImage.getWidth(); - double fullCardImgHeight = faceArtImage.getHeight(); - double artWidth = fullCardImgWidth; - double artHeight = fullCardImgHeight; - double targetWidth = w; - double targetHeight = h; - double targetAspect = targetWidth / targetHeight; - if (!shouldPreserveAspect) { - // No adjustment to art - } else if (targetAspect * artHeight < artWidth) { - // Trim off some width - artWidth = targetAspect * artHeight; - } else { - // Trim off some height - artHeight = artWidth / targetAspect; - } - try { - /*BufferedImage subImg - = faceArtImage.getSubimage( - (int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight), - (int) artWidth, (int) artHeight);*/ - RenderingHints rh = new RenderingHints( - RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BICUBIC); - g.setRenderingHints(rh); - if (fullCardImgWidth > fullCardImgHeight) { - g.drawImage(faceArtImage, - x, y, - (int) targetWidth, (int) targetHeight, - null); - } else { - g.drawImage(faceArtImage, - x, y, - (int) targetWidth, alternate_h, // alernate_h is roughly (targetWidth / 0.74) - null); - lessOpaqueRulesTextBox = true; - } - } catch (RasterFormatException e) { - // At very small card sizes we may encounter a problem with rounding error making the rect not fit - System.out.println(e); - } - } // Draw +1/+1 and other counters protected void drawCounters(Graphics2D g) { @@ -532,10 +484,4 @@ public abstract class CardRenderer { public void setArtImage(Image image) { artImage = CardRendererUtils.toBufferedImage(image); } - - // Set the card art image (CardPanel will give it to us when it - // is loaded and ready) - public void setFaceArtImage(Image image) { - faceArtImage = CardRendererUtils.toBufferedImage(image); - } } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererFactory.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererFactory.java index 0c206ee8aee..3d3f9c2e811 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererFactory.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererFactory.java @@ -1,6 +1,5 @@ package org.mage.card.arcane; -import mage.cards.ArtRect; import mage.view.CardView; /** @@ -12,9 +11,7 @@ public class CardRendererFactory { } public CardRenderer create(CardView card) { - if (card.isSplitCard() && card.getArtRect() != ArtRect.SPLIT_FUSED) { - // Split fused cards still render with the normal frame, showing all abilities - // from both halves in one frame. + if (card.isSplitCard()) { return new ModernSplitCardRenderer(card); } else { return new ModernCardRenderer(card); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java index 5600c59b812..32cd121e339 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/GlowText.java @@ -28,7 +28,7 @@ public class GlowText extends JLabel { private Color glowColor; private boolean wrap; private int lineCount = 0; - private static final SoftValuesLoadingCache IMAGE_CACHE; + private static final SoftValuesLoadingCache GLOW_TEXT_IMAGES_CACHE; private static final class Key { @@ -122,7 +122,7 @@ public class GlowText extends JLabel { } static { - IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(GlowText::createGlowImage)); + GLOW_TEXT_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(GlowText::createGlowImage)); } public void setGlow(Color glowColor, int size, float intensity) { @@ -153,7 +153,7 @@ public class GlowText extends JLabel { } public BufferedImage getGlowImage() { - return IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)); + return GLOW_TEXT_IMAGES_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)); } private static BufferedImage createGlowImage(Key key) { diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index 00101fe5554..eb2643061dc 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -15,14 +15,10 @@ import org.apache.log4j.Logger; import static org.mage.card.arcane.ManaSymbols.getSizedManaSymbol; import static org.mage.card.arcane.ModernCardResourceLoader.*; -import javax.swing.*; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.CharacterIterator; @@ -30,27 +26,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; - -/* - private void cardRendererBasedRender(Graphics2D g) { - // Prepare for draw - g.translate(cardXOffset, cardYOffset); - int cardWidth = this.cardWidth - cardXOffset; - int cardHeight = this.cardHeight - cardYOffset; - - // AA on - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // Renderer - CardRenderer render = new ModernCardRenderer(gameCard, transformed); - Image img = imagePanel.getSrcImage(); - if (img != null) { - render.setArtImage(img); - } - render.draw(g, cardWidth, cardHeight); - } - */ - /** * @author stravant@gmail.com, JayDi85 *

@@ -147,6 +122,8 @@ public class ModernCardRenderer extends CardRenderer { public static final Color ERROR_COLOR = new Color(255, 0, 255); + static String SUB_TYPE_ADVENTURE = "Adventure"; + /////////////////////////////////////////////////////////////////////////// // Layout metrics for modern border cards // How far the main box, art, and name / type line are inset from the @@ -171,6 +148,7 @@ public class ModernCardRenderer extends CardRenderer { protected static final float TYPE_LINE_Y_FRAC = 0.57f; // x cardHeight protected static final float TYPE_LINE_Y_FRAC_TOKEN = 0.70f; protected static final float TYPE_LINE_Y_FRAC_FULL_ART = 0.74f; + protected static final float TYPE_LINE_Y_FRAC_BOTTOM = 0.89f; protected int typeLineY; // Possible sizes of rules text font @@ -188,7 +166,10 @@ public class ModernCardRenderer extends CardRenderer { protected Font ptTextFont; // Processed mana cost string - protected final String manaCostString; + protected String manaCostString; + + // Is an adventure + protected boolean isAdventure = false; public ModernCardRenderer(CardView card) { // Pass off to parent @@ -196,6 +177,14 @@ public class ModernCardRenderer extends CardRenderer { // Mana cost string manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr()); + + if (cardView.isSplitCard()) { + isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE); + } + } + + protected boolean isAdventure() { + return isAdventure; } @Override @@ -274,10 +263,9 @@ public class ModernCardRenderer extends CardRenderer { @Override protected void drawBackground(Graphics2D g) { // Draw background, in 3 parts - if (cardView.isFaceDown()) { - // Just draw a brown rectangle - drawCardBack(g); + // just draw a brown rectangle + drawCardBackTexture(g); } else { if (cardView.getFrameStyle() == FrameStyle.UST_FULL_ART_BASIC) { return; @@ -287,7 +275,7 @@ public class ModernCardRenderer extends CardRenderer { if (cardView.getExpansionSetCode().equals("EXP")) { isExped = true; } - BufferedImage bg = getBackgroundImage(cardView.getColor(), cardView.getCardTypes(), cardView.getSubTypes(), isExped); + BufferedImage bg = getBackgroundTexture(cardView.getColor(), cardView.getCardTypes(), cardView.getSubTypes(), isExped); if (bg == null) { return; } @@ -327,6 +315,9 @@ public class ModernCardRenderer extends CardRenderer { rect = new Rectangle2D.Float(.079f, .11f, .84f, .84f); } else if (isUnstableFullArtLand()) { rect = new Rectangle2D.Float(.0f, .0f, 1.0f, 1.0f); + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT || + cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) { + rect = cardView.getArtRect().rect; } else if (cardView.getFrameStyle().isFullArt() || (cardView.isToken())) { rect = new Rectangle2D.Float(.079f, .11f, .84f, .63f); } else { @@ -340,6 +331,9 @@ public class ModernCardRenderer extends CardRenderer { return TYPE_LINE_Y_FRAC_TOKEN; } else if (cardView.getFrameStyle().isFullArt()) { return TYPE_LINE_Y_FRAC_FULL_ART; + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT || + cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) { + return TYPE_LINE_Y_FRAC_BOTTOM; } else { return TYPE_LINE_Y_FRAC; } @@ -376,16 +370,9 @@ public class ModernCardRenderer extends CardRenderer { @Override protected void drawArt(Graphics2D g) { - if ((artImage != null || faceArtImage != null) && !cardView.isFaceDown()) { - - boolean useFaceArt = false; - if (faceArtImage != null && !isZendikarFullArtLand()) { - useFaceArt = true; - } - + if (artImage != null) { // Invention rendering, art fills the entire frame if (useInventionFrame()) { - useFaceArt = false; drawArtIntoRect(g, borderWidth, borderWidth, cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth, @@ -396,34 +383,24 @@ public class ModernCardRenderer extends CardRenderer { Rectangle2D sourceRect = getArtRect(); if (cardView.getMageObjectType() == MageObjectType.SPELL) { - useFaceArt = false; ArtRect rect = cardView.getArtRect(); - if (rect == ArtRect.SPLIT_FUSED) { - // Special handling for fused, draw the art from both halves stacked on top of one and other - // each filling half of the art rect - drawArtIntoRect(g, - totalContentInset + 1, totalContentInset + boxHeight, - contentWidth - 2, (typeLineY - totalContentInset - boxHeight) / 2, - ArtRect.SPLIT_LEFT.rect, useInventionFrame()); - drawArtIntoRect(g, - totalContentInset + 1, totalContentInset + boxHeight + (typeLineY - totalContentInset - boxHeight) / 2, - contentWidth - 2, (typeLineY - totalContentInset - boxHeight) / 2, - ArtRect.SPLIT_RIGHT.rect, useInventionFrame()); - return; - } else if (rect != ArtRect.NORMAL) { + if (rect != ArtRect.NORMAL) { sourceRect = rect.rect; shouldPreserveAspect = false; } } // Normal drawing of art from a source part of the card frame into the rect - if (useFaceArt) { - int alternate_height = cardHeight - boxHeight * 2 - totalContentInset; - drawFaceArtIntoRect(g, + if (cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) { + drawArtIntoRect(g, + contentWidth / 2 + totalContentInset + 1, totalContentInset + boxHeight, + contentWidth / 2 - 1, typeLineY - totalContentInset - boxHeight, + sourceRect, false); + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT) { + drawArtIntoRect(g, totalContentInset + 1, totalContentInset + boxHeight, - contentWidth - 2, typeLineY - totalContentInset - boxHeight, - alternate_height, - sourceRect, shouldPreserveAspect); + contentWidth / 2 - 1, typeLineY - totalContentInset - boxHeight, + sourceRect, false); } else if (!isZendikarFullArtLand()) { drawArtIntoRect(g, totalContentInset + 1, totalContentInset + boxHeight, @@ -474,7 +451,13 @@ public class ModernCardRenderer extends CardRenderer { g.setPaint(textboxPaint); } - if (!isZenUst) { + if (cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) { + g.fillRect(totalContentInset + 2, totalContentInset + boxHeight, + contentWidth / 2 - 2, typeLineY - totalContentInset - boxHeight + 2); + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT) { + g.fillRect(contentWidth / 2 + totalContentInset + 1, totalContentInset + boxHeight, + contentWidth / 2 - 2, typeLineY - totalContentInset - boxHeight + 2); + } else if (!isZenUst) { if (cardView.getCardTypes().contains(CardType.LAND)) { int total_height_of_box = cardHeight - borderWidth * 3 - typeLineY - 2 - boxHeight; @@ -669,6 +652,18 @@ public class ModernCardRenderer extends CardRenderer { drawUSTCurves(g, image, x, y, w, h, 0, 0, additionalBoxColor, borderPaint); + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) { + drawRulesText(g, textboxKeywords, textboxRules, + totalContentInset + 4, totalContentInset + boxHeight + 2, + contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); + } else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_LEFT) { + drawRulesText(g, textboxKeywords, textboxRules, + contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2, + contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); + } else if (isAdventure) { + drawRulesText(g, textboxKeywords, textboxRules, + contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2, + contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false); } else if (!isZenUst) { drawRulesText(g, textboxKeywords, textboxRules, totalContentInset + 2, typeLineY + boxHeight + 2, @@ -681,27 +676,20 @@ public class ModernCardRenderer extends CardRenderer { public void drawZendikarCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2, Color boxColor, Paint paint) { - - BufferedImage artToUse = faceArtImage; - boolean hadToUseFullArt = false; - if (faceArtImage == null) { - if (artImage == null) { - return; - } - hadToUseFullArt = true; - artToUse = artImage; + if (artImage == null) { + return; } + + BufferedImage artToUse = artImage; int srcW = artToUse.getWidth(); int srcH = artToUse.getHeight(); - if (hadToUseFullArt) { - // Get a box based on the standard scan from gatherer. - // Width = 185/223 pixels (centered) - // Height = 220/310, 38 pixels from top - int subx = 19 * srcW / 223; - int suby = 38 * srcH / 310; - artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310); - } + // Get a box based on the standard scan from gatherer. + // Width = 185/223 pixels (centered) + // Height = 220/310, 38 pixels from top + int subx = 19 * srcW / 223; + int suby = 38 * srcH / 310; + artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310); Path2D.Double curve = new Path2D.Double(); @@ -730,26 +718,19 @@ public class ModernCardRenderer extends CardRenderer { public void drawBFZCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2, int topxdelta, int endydelta, Color boxColor, Paint paint) { - BufferedImage artToUse = faceArtImage; - boolean hadToUseFullArt = false; - if (faceArtImage == null) { - if (artImage == null) { - return; - } - hadToUseFullArt = true; - artToUse = artImage; + if (artImage == null) { + return; } + BufferedImage artToUse = artImage; int srcW = artToUse.getWidth(); int srcH = artToUse.getHeight(); - if (hadToUseFullArt) { - // Get a box based on the standard scan from gatherer. - // Width = 185/223 pixels (centered) - // Height = 220/310, 38 pixels from top - int subx = 19 * srcW / 223; - int suby = 38 * srcH / 310; - artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310); - } + // Get a box based on the standard scan from gatherer. + // Width = 185/223 pixels (centered) + // Height = 220/310, 38 pixels from top + int subx = 19 * srcW / 223; + int suby = 38 * srcH / 310; + artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310); Path2D.Double curve = new Path2D.Double(); curve.moveTo(x + topxdelta, y); @@ -875,23 +856,13 @@ public class ModernCardRenderer extends CardRenderer { int availableWidth = w - manaCostWidth + 2; // Draw the name - String nameStr; - if (cardView.isFaceDown()) { - if (cardView instanceof PermanentView && ((PermanentView) cardView).isManifested()) { - nameStr = "Manifest: " + cardView.getName(); - } else { - nameStr = "Morph: " + cardView.getName(); - } - } else { - nameStr = baseName; - } - if (!nameStr.isEmpty()) { - AttributedString str = new AttributedString(nameStr); + if (!baseName.isEmpty()) { + AttributedString str = new AttributedString(baseName); str.addAttribute(TextAttribute.FONT, boxTextFont); TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); int breakIndex = measure.getLineBreakIndex(0, availableWidth); - if (breakIndex < nameStr.length()) { - str = new AttributedString(nameStr); + if (breakIndex < baseName.length()) { + str = new AttributedString(baseName); str.addAttribute(TextAttribute.FONT, boxTextFontNarrow); measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); breakIndex = measure.getLineBreakIndex(0, availableWidth); @@ -1568,7 +1539,7 @@ public class ModernCardRenderer extends CardRenderer { // Determine which background image to use from a set of colors // and the current card. - protected static BufferedImage getBackgroundImage(ObjectColor colors, Collection types, SubTypes subTypes, boolean isExped) { + protected static BufferedImage getBackgroundTexture(ObjectColor colors, Collection types, SubTypes subTypes, boolean isExped) { if (subTypes.contains(SubType.VEHICLE)) { return BG_IMG_VEHICLE; } else if (types.contains(CardType.LAND)) { diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java index 70aeef72643..824589c64a9 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java @@ -18,6 +18,14 @@ import java.util.List; */ public class ModernSplitCardRenderer extends ModernCardRenderer { + public static final Color ADVENTURE_BOX_WHITE = new Color(135, 122, 103); + public static final Color ADVENTURE_BOX_BLUE = new Color(2, 96, 131); + public static final Color ADVENTURE_BOX_BLACK = new Color(52, 44, 46); + public static final Color ADVENTURE_BOX_RED = new Color(126, 61, 42); + public static final Color ADVENTURE_BOX_GREEN = new Color(9, 51, 30); + public static final Color ADVENTURE_BOX_GOLD = new Color(118, 92, 42); + public static final Color ADVENTURE_BOX_COLORLESS = new Color(131, 133, 135); + static String RULES_MARK_FUSE = "Fuse"; static String RULES_MARK_AFTERMATH = "Aftermath"; @@ -29,8 +37,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { String typeLineString; String manaCostString; ObjectColor color; - List rules = new ArrayList<>(); - List keywords = new ArrayList<>(); + ArrayList rules = new ArrayList<>(); + ArrayList keywords = new ArrayList<>(); } private static final List ONLY_LAND_TYPE = Arrays.asList(CardType.LAND); @@ -47,6 +55,13 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { private boolean isFuse = false; private boolean isAftermath = false; + private static String trimAdventure(String rule) { + if (rule.startsWith("Adventure")) { + return rule.substring(rule.lastIndexOf("—") + 8); + } + return rule; + } + public ModernSplitCardRenderer(CardView view) { super(view); @@ -56,7 +71,15 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr()); leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr()); - parseRules(view.getRightSplitRules(), rightHalf.keywords, rightHalf.rules); + if (isAdventure()) { + List trimmedRules = new ArrayList<>(); + for (String rule : view.getRightSplitRules()) { + trimmedRules.add(trimAdventure(rule)); + } + parseRules(trimmedRules, rightHalf.keywords, rightHalf.rules); + } else { + parseRules(view.getRightSplitRules(), rightHalf.keywords, rightHalf.rules); + } parseRules(view.getLeftSplitRules(), leftHalf.keywords, leftHalf.rules); rightHalf.typeLineString = cardView.getRightSplitTypeLine(); @@ -71,7 +94,12 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { // It's easier for rendering to swap the card halves here because for aftermath cards // they "rotate" in opposite directions making consquence and normal split cards // have the "right" vs "left" as the top half. - if (!isAftermath()) { + // Adventures are treated differently and not rotated at all. + if (isAdventure()) { + manaCostString = leftHalf.manaCostString; + textboxKeywords = leftHalf.keywords; + textboxRules = leftHalf.rules; + } else if (!isAftermath()) { HalfCardProps tmp = leftHalf; leftHalf = rightHalf; rightHalf = tmp; @@ -130,7 +158,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawBackground(Graphics2D g) { if (cardView.isFaceDown()) { - drawCardBack(g); + drawCardBackTexture(g); + } if (isAdventure()) { + super.drawBackground(g); } else { { // Left half background (top of the card) // Set texture to paint the left with @@ -174,7 +204,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawArt(Graphics2D g) { - if (artImage != null && !cardView.isFaceDown()) { + if (isAdventure) { + super.drawArt(g); + } else if (artImage != null) { if (isAftermath()) { Rectangle2D topRect = ArtRect.AFTERMATH_TOP.rect; int topLineY = (int) (leftHalf.ch * TYPE_LINE_Y_FRAC); @@ -286,7 +318,43 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { - if (isAftermath()) { + if (isAdventure()) { + super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox); + + CardPanelAttributes adventureAttribs = new CardPanelAttributes( + attribs.cardWidth, attribs.cardHeight, attribs.isChoosable, + attribs.isSelected, true); + + // Draw the adventure name line box + g.setPaint(getBoxColor(rightHalf.color, cardView.getCardTypes(), true)); + g.fillRect(totalContentInset, typeLineY + boxHeight + 1, + contentWidth / 2 - 1, boxHeight - 2); + + // Draw the adventure type line box + g.setPaint(getAdventureBoxColor(rightHalf.color)); + g.fillRect(totalContentInset , typeLineY + boxHeight * 2 - 1, + contentWidth / 2 - 1, boxHeight - 2); + + // Draw the adventure text box + g.setPaint(getTextboxPaint(rightHalf.color, cardView.getCardTypes(), cardWidth, lessOpaqueRulesTextBox)); + g.fillRect(totalContentInset, typeLineY + boxHeight * 3 - 3, + contentWidth / 2 - 1, cardHeight - borderWidth * 3 - typeLineY - boxHeight * 3 + 2); + + // Draw the adventure name line + drawNameLine(g, adventureAttribs, rightHalf.name, rightHalf.manaCostString, + totalContentInset + 2, typeLineY + boxHeight, + contentWidth / 2 - 8, boxHeight - 2); + + // Draw the adventure type line + drawTypeLine(g, adventureAttribs, rightHalf.typeLineString, + totalContentInset + 2, typeLineY + boxHeight * 2 - 2, + contentWidth / 2 - 8, boxHeight - 2, true); + + // Draw the adventure textbox rules + drawRulesText(g, rightHalf.keywords, rightHalf.rules, + totalContentInset + 3, typeLineY + boxHeight * 3 - 1, + contentWidth / 2 - 8, cardHeight - borderWidth * 3 - typeLineY - boxHeight * 3 + 2, false); + } else if (isAftermath()) { drawSplitHalfFrame(getUnmodifiedHalfContext(g), attribs, leftHalf, (int) (leftHalf.ch * TYPE_LINE_Y_FRAC)); drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2); } else { @@ -308,4 +376,24 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { } } } + + protected Color getAdventureBoxColor(ObjectColor colors) { + if (colors.isMulticolored()) { + return ADVENTURE_BOX_GOLD; + } else if (colors.isColorless()) { + return ADVENTURE_BOX_COLORLESS; + } else if (colors.isWhite()) { + return ADVENTURE_BOX_WHITE; + } else if (colors.isBlue()) { + return ADVENTURE_BOX_BLUE; + } else if (colors.isBlack()) { + return ADVENTURE_BOX_BLACK; + } else if (colors.isRed()) { + return ADVENTURE_BOX_RED; + } else if (colors.isGreen()) { + return ADVENTURE_BOX_GREEN; + } else { + return ERROR_COLOR; + } + } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java index fc5cd5f4371..e075e4c058a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java @@ -4,6 +4,7 @@ import mage.cards.MageCard; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; import mage.client.util.GUISizeHelper; +import mage.client.util.ImageCaches; import mage.interfaces.plugin.CardPlugin; import mage.view.CardView; import mage.view.CounterView; @@ -667,7 +668,7 @@ public class CardPluginImpl implements CardPlugin { LOGGER.info("Symbols download finished"); dialog.dispose(); ManaSymbols.loadImages(); - ImageCache.clearCache(); + GUISizeHelper.refreshGUIAndCards(); } } } @@ -710,6 +711,6 @@ public class CardPluginImpl implements CardPlugin { @Override public BufferedImage getOriginalImage(CardView card) { - return ImageCache.getImageOriginal(card).getImage(); + return ImageCache.getCardImageOriginal(card).getImage(); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java index b347011e1b5..a0221c4e100 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java @@ -24,15 +24,17 @@ public class DownloadJob extends AbstractLaternaBean { private final String name; private Source source; private final Destination destination; + private final boolean forceToDownload; // download image everytime, do not keep old image private final Property state = properties.property("state", State.NEW); private final Property message = properties.property("message"); private final Property error = properties.property("error"); private final BoundedRangeModel progress = new DefaultBoundedRangeModel(); - public DownloadJob(String name, Source source, Destination destination) { + public DownloadJob(String name, Source source, Destination destination, boolean forceToDownload) { this.name = name; this.source = source; this.destination = destination; + this.forceToDownload = forceToDownload; } /** @@ -155,6 +157,10 @@ public class DownloadJob extends AbstractLaternaBean { return destination; } + public boolean isForceToDownload() { + return forceToDownload; + } + public static Source fromURL(final String url) { return fromURL(CardImageUtils.getProxyFromPreferences(), url); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java index 815083b9013..c4af2b2d452 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java @@ -146,7 +146,7 @@ public class Downloader extends AbstractLaternaBean { Destination dst = job.getDestination(); BoundedRangeModel progress = job.getProgress(); - if (dst.isValid()) { + if (dst.isValid() && !job.isForceToDownload()) { // already done progress.setMaximum(1); progress.setValue(1); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardFrames.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardFrames.java deleted file mode 100644 index 37c81e59dfe..00000000000 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardFrames.java +++ /dev/null @@ -1,76 +0,0 @@ - -package org.mage.plugins.card.dl.sources; - -import java.io.File; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.mage.plugins.card.dl.DownloadJob; -import static org.mage.plugins.card.dl.DownloadJob.fromURL; -import static org.mage.plugins.card.dl.DownloadJob.toFile; - -/** - * - * @author LevelX2 - */ - -public class CardFrames implements Iterable { - - private static final String FRAMES_PATH = File.separator + "frames"; - private static final File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + FRAMES_PATH); - private static File outDir = DEFAULT_OUT_DIR; - - static final String BASE_DOWNLOAD_URL = "http://ct-magefree.rhcloud.com/resources/img/"; - static final String TEXTURES_FOLDER = "textures"; - static final String PT_BOXES_FOLDER = "pt"; - - private static final String[] TEXTURES = {"U", "R", "G", "B", "W", "A", - "BG_LAND", "BR_LAND", "WU_LAND", "WB_LAND", "UB_LAND", "GW_LAND", "RW_LAND", - "RG_LAND", "GU_LAND", "UR_LAND" - // NOT => "BW_LAND","BU_LAND","WG_LAND","WR_LAND", - }; - private static final String[] PT_BOXES = {"U", "R", "G", "B", "W", "A"}; - - public CardFrames(String path) { - if (path == null) { - useDefaultDir(); - } else { - changeOutDir(path); - } - } - - @Override - public Iterator iterator() { - List jobs = new ArrayList<>(); - for (String texture : TEXTURES) { - jobs.add(generateDownloadJob(TEXTURES_FOLDER, texture)); - } - for (String pt_box : PT_BOXES) { - jobs.add(generateDownloadJob(PT_BOXES_FOLDER, pt_box)); - } - return jobs.iterator(); - } - - private DownloadJob generateDownloadJob(String dirName, String name) { - File dst = new File(outDir, name + ".png"); - String url = BASE_DOWNLOAD_URL + dirName + '/' + name + ".png"; - return new DownloadJob("frames-" + dirName + '-' + name, fromURL(url), toFile(dst)); - } - - private void useDefaultDir() { - outDir = DEFAULT_OUT_DIR; - } - - private void changeOutDir(String path) { - File file = new File(path + FRAMES_PATH); - if (file.exists()) { - outDir = file; - } else { - file.mkdirs(); - if (file.exists()) { - outDir = file; - } - } - } -} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/DirectLinksForDownload.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/DirectLinksForDownload.java index 915ed43a5db..0c3acc8068c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/DirectLinksForDownload.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/DirectLinksForDownload.java @@ -11,7 +11,7 @@ import static org.mage.plugins.card.dl.DownloadJob.toFile; import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; /** - * Used when we need to point to direct links to download resources from. + * Additional images from a third party sources * * @author noxx */ @@ -19,12 +19,13 @@ public class DirectLinksForDownload implements Iterable { private static final Map directLinks = new LinkedHashMap<>(); + // face down cards uses tokens source for a cardback image + // that's cardback used for miss images + // TODO: replace miss image cardback to some generic card (must be diff from face down image) public static final String cardbackFilename = "cardback.jpg"; - public static final String foretellFilename = "foretell.jpg"; static { directLinks.put(cardbackFilename, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg"); - directLinks.put(foretellFilename, "https://api.scryfall.com/cards/tkhm/23/en?format=image"); } private final File outDir; @@ -42,7 +43,8 @@ public class DirectLinksForDownload implements Iterable { for (Map.Entry url : directLinks.entrySet()) { File dst = new File(outDir, url.getKey()); - jobs.add(new DownloadJob(url.getKey(), fromURL(url.getValue()), toFile(dst))); + // download images every time (need to update low quality image) + jobs.add(new DownloadJob(url.getKey(), fromURL(url.getValue()), toFile(dst), true)); } return jobs.iterator(); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index db0aaeecac6..bb59cc2bdfa 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -337,6 +337,6 @@ public class GathererSets implements Iterable { set = codeReplacements.get(set); } String url = "https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity; - return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst)); + return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst), false); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index 72647d60732..fa073dda3eb 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -125,7 +125,7 @@ public class GathererSymbols implements Iterable { String url = format(urlFmt, sizes[modSizeIndex], symbol); - return new DownloadJob(sym, fromURL(url), toFile(dst)); + return new DownloadJob(sym, fromURL(url), toFile(dst), false); } } }; diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index ec0366c3a39..15b58188df2 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -96,15 +96,13 @@ public enum ScryfallImageSource implements CardImageSource { } // double faced cards (modal double faces cards too) - if (card.isTwoFacedCard()) { - if (card.isSecondSide()) { - // back face - must be prepared before - logger.warn("Can't find back face info in prepared list " - + card.getName() + " (" + card.getSet() + ") #" + card.getCollectorId()); - return new CardImageUrls(null, null); - } else { - // front face - can be downloaded normally as basic card - } + if (card.isSecondSide()) { + // back face - must be prepared before + logger.warn("Can't find back face info in prepared list " + + card.getName() + " (" + card.getSet() + ") #" + card.getCollectorId()); + return new CardImageUrls(null, null); + } else { + // front face - can be downloaded normally as basic card } // basic cards by api call (redirect to img link) @@ -219,7 +217,7 @@ public enum ScryfallImageSource implements CardImageSource { int needPrepareCount = 0; int currentPrepareCount = 0; for (CardDownloadData card : downloadList) { - if (card.isTwoFacedCard() && card.isSecondSide()) { + if (card.isSecondSide()) { needPrepareCount++; } } @@ -232,7 +230,7 @@ public enum ScryfallImageSource implements CardImageSource { } // prepare the back face URL - if (card.isTwoFacedCard() && card.isSecondSide()) { + if (card.isSecondSide()) { currentPrepareCount++; try { String url = getFaceImageUrl(proxy, card, card.isToken()); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index b1454cd3ef3..e670880fba1 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -404,7 +404,7 @@ public class ScryfallImageSupportCards { add("XANA"); // Arena New Player Experience Extras add("OANA"); // Arena New Player Experience Cards add("PS18"); // San Diego Comic-Con 2018 - //add("HTR17"); // Heroes of the Realm 2017 + add("PH17"); // Heroes of the Realm 2017 add("C18"); // Commander 2018 add("PGRN"); // Guilds of Ravnica Promos add("PRWK"); // GRN Ravnica Weekend @@ -439,7 +439,7 @@ public class ScryfallImageSupportCards { add("ELD"); // Throne of Eldraine //add("PTG"); // Ponies: The Galloping add("CMB1"); // Mystery Booster Playtest Cards - add("MB1"); // Mystery Booster + //add("MB1"); // Mystery Booster add("GN2"); // Game Night 2019 add("HA1"); // Historic Anthology 1 //add("HHO"); // Happy Holidays @@ -453,7 +453,7 @@ public class ScryfallImageSupportCards { add("PWOR"); // World Championship Promos add("PANA"); // MTG Arena Promos add("UND"); // Unsanctioned - add("FMB1"); // Mystery Booster Retail Edition Foils + //add("FMB1"); // Mystery Booster Retail Edition Foils add("HA2"); // Historic Anthology 2 add("SLD"); // Secret Lair Drop add("PMEI"); // Magazine Inserts @@ -472,6 +472,7 @@ public class ScryfallImageSupportCards { add("PH19"); // 2019 Heroes of the Realm add("2XM"); // Double Masters add("AKR"); // Amonkhet Remastered + add("ANB"); // Arena Beginner Set add("ZNR"); // Zendikar Rising add("ZNC"); // Zendikar Rising Commander add("ZNE"); // Zendikar Rising Expeditions @@ -517,11 +518,13 @@ public class ScryfallImageSupportCards { add("BRR"); // The Brothers' War Retro Artifacts add("BOT"); // Transformers add("J22"); // Jumpstart 2022 + add("SCD"); // Starter Commander Decks add("SLC"); // Secret Lair 30th Anniversary Countdown Kit add("DMR"); // Dominaria Remastered add("ONE"); // Phyrexia: All Will Be One add("ONC"); // Phyrexia: All Will Be One Commander add("PL23"); // Year of the Rabbit 2023 + add("SLP"); // Secret Lair Showdown add("MOM"); // March of the Machine add("MOC"); // March of the Machine Commander add("MAT"); // March of the Machine: The Aftermath @@ -543,6 +546,11 @@ public class ScryfallImageSupportCards { add("MKM"); // Murders at Karlov Manor add("MKC"); // Murders at Karlov Manor Commander add("CLU"); // Ravnica: Clue Edition + add("OTJ"); // Outlaws of Thunder Junction + add("BIG"); // The Big Score + add("MH3"); // Modern Horizons 3 + add("ACR"); // Assassin's Creed + add("BLB"); // Bloomburrow // Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks add("CALC"); // Custom Alchemized versions of existing cards @@ -814,6 +822,8 @@ public class ScryfallImageSupportCards { put("PJOU/Heroes' Bane/126*", "https://api.scryfall.com/cards/pjou/126★/"); put("PJOU/Scourge of Fleets/51*", "https://api.scryfall.com/cards/pjou/51★/"); put("PJOU/Spawn of Thraxes/112*", "https://api.scryfall.com/cards/pjou/112★/"); + // PL21 + put("PL21/Sethron, Hurloon General/1*", "https://api.scryfall.com/cards/pl21/1★/"); // PM10 put("PM10/Ant Queen/166*", "https://api.scryfall.com/cards/pm10/166★/"); put("PM10/Honor of the Pure/16*", "https://api.scryfall.com/cards/pm10/16★/"); @@ -842,11 +852,14 @@ public class ScryfallImageSupportCards { put("PMBS/Glissa, the Traitor/96*", "https://api.scryfall.com/cards/pmbs/96★/"); put("PMBS/Hero of Bladehold/8*", "https://api.scryfall.com/cards/pmbs/8★/"); put("PMBS/Thopter Assembly/140*", "https://api.scryfall.com/cards/pmbs/140★/"); + // PMEI + put("PMEI/Jamuraan Lion/10*", "https://api.scryfall.com/cards/pmei/10★/"); // PNPH put("PNPH/Phyrexian Metamorph/42*", "https://api.scryfall.com/cards/pnph/42★/"); put("PNPH/Sheoldred, Whispering One/73*", "https://api.scryfall.com/cards/pnph/73★/"); // PRES put("PRES/Goblin Chieftain/141*", "https://api.scryfall.com/cards/pres/141★/"); + put("PRES/Lathliss, Dragon Queen/149*", "https://api.scryfall.com/cards/pres/149★/"); put("PRES/Loam Lion/13*", "https://api.scryfall.com/cards/pres/13★/"); put("PRES/Oran-Rief, the Vastwood/221*", "https://api.scryfall.com/cards/pres/221★/"); // PROE @@ -998,27 +1011,42 @@ public class ScryfallImageSupportCards { put("WAR/Vraska, Swarm's Eminence/236*", "https://api.scryfall.com/cards/war/236★/"); // SLD // fake double faced cards - put("SLD/Zndrsplt, Eye of Wisdom/379", "https://api.scryfall.com/cards/sld/379/"); - put("SLD/Zndrsplt, Eye of Wisdom/379b", "https://c1.scryfall.com/file/scryfall-cards/large/back/d/5/d5dfd236-b1da-4552-b94f-ebf6bb9dafdf.jpg"); - put("SLD/Krark's Thumb/383", "https://api.scryfall.com/cards/sld/383/"); - put("SLD/Krark's Thumb/383b", "https://c1.scryfall.com/file/scryfall-cards/large/back/9/f/9f63277b-e139-46c8-b9e3-0cfb647f44cc.jpg"); - put("SLD/Okaun, Eye of Chaos/380", "https://api.scryfall.com/cards/sld/380/"); - put("SLD/Okaun, Eye of Chaos/380b", "https://c1.scryfall.com/file/scryfall-cards/large/back/9/4/94eea6e3-20bc-4dab-90ba-3113c120fb90.jpg"); - put("SLD/Propaganda/381", "https://api.scryfall.com/cards/sld/381/"); - put("SLD/Propaganda/381b", "https://c1.scryfall.com/file/scryfall-cards/large/back/3/e/3e3f0bcd-0796-494d-bf51-94b33c1671e9.jpg"); - put("SLD/Stitch in Time/382", "https://api.scryfall.com/cards/sld/382/"); - put("SLD/Stitch in Time/382b", "https://c1.scryfall.com/file/scryfall-cards/large/back/0/8/087c3a0d-c710-4451-989e-596b55352184.jpg"); + put("SLD/Blightsteel Colossus/1079b", "https://api.scryfall.com/cards/sld/1079/en?format=image&face=back"); + put("SLD/Doubling Cube/1080b", "https://api.scryfall.com/cards/sld/1080/en?format=image&face=back"); + put("SLD/Darksteel Colossus/1081b", "https://api.scryfall.com/cards/sld/1081/en?format=image&face=back"); + put("SLD/Ulamog, the Ceaseless Hunger/1122b", "https://api.scryfall.com/cards/sld/1122/en?format=image&face=back"); + put("SLD/Etali, Primal Storm/1123b", "https://api.scryfall.com/cards/sld/1123/en?format=image&face=back"); + put("SLD/Ghalta, Primal Hunger/1124b", "https://api.scryfall.com/cards/sld/1124/en?format=image&face=back"); + put("SLD/Zndrsplt, Eye of Wisdom/379b", "https://api.scryfall.com/cards/sld/379/en?format=image&face=back"); + put("SLD/Zndrsplt, Eye of Wisdom/379*", "https://api.scryfall.com/cards/sld/379★/"); + put("SLD/Zndrsplt, Eye of Wisdom/379b*", "https://api.scryfall.com/cards/sld/379★/en?format=image&face=back"); + put("SLD/Ajani Goldmane/1453b", "https://api.scryfall.com/cards/sld/1453/en?format=image&face=back"); + put("SLD/Jace Beleren/1454b", "https://api.scryfall.com/cards/sld/1454/en?format=image&face=back"); + put("SLD/Liliana Vess/1455b", "https://api.scryfall.com/cards/sld/1455/en?format=image&face=back"); + put("SLD/Chandra Nalaar/1456b", "https://api.scryfall.com/cards/sld/1456/en?format=image&face=back"); + put("SLD/Garruk Wildspeaker/1457b", "https://api.scryfall.com/cards/sld/1457/en?format=image&face=back"); + put("SLD/Okaun, Eye of Chaos/380b", "https://api.scryfall.com/cards/sld/380/en?format=image&face=back"); + put("SLD/Okaun, Eye of Chaos/380*", "https://api.scryfall.com/cards/sld/380★/"); + put("SLD/Okaun, Eye of Chaos/380b*", "https://api.scryfall.com/cards/sld/380★/en?format=image&face=back"); + put("SLD/Propaganda/381b", "https://api.scryfall.com/cards/sld/381/en?format=image&face=back"); + put("SLD/Stitch in Time/382b", "https://api.scryfall.com/cards/sld/382/en?format=image&face=back"); + put("SLD/Krark's Thumb/383b", "https://api.scryfall.com/cards/sld/383/en?format=image&face=back"); // normal cards put("SLD/Demonlord Belzenlok/159*", "https://api.scryfall.com/cards/sld/159★/"); put("SLD/Griselbrand/160*", "https://api.scryfall.com/cards/sld/160★/"); put("SLD/Kothophed, Soul Hoarder/162*", "https://api.scryfall.com/cards/sld/162★/"); put("SLD/Liliana's Contract/161*", "https://api.scryfall.com/cards/sld/161★/"); put("SLD/Razaketh, the Foulblooded/163*", "https://api.scryfall.com/cards/sld/163★/"); - // PMEI - put("PMEI/Jamuraan Lion/10*", "https://api.scryfall.com/cards/pmei/10★/"); - // PRES - put("PRES/Lathliss, Dragon Queen/149*", "https://api.scryfall.com/cards/pres/149★/"); - + put("SLD/Goblin Lackey/1311*", "https://api.scryfall.com/cards/sld/1311★/"); + put("SLD/Goblin Matron/1312*", "https://api.scryfall.com/cards/sld/1312★/"); + put("SLD/Muxus, Goblin Grandee/1314*", "https://api.scryfall.com/cards/sld/1314★/"); + put("SLD/Goblin Recruiter/1313*", "https://api.scryfall.com/cards/sld/1313★/"); + put("SLD/Plague Sliver/633Ph", "https://api.scryfall.com/cards/sld/633Φ/"); + put("SLD/Shadowborn Apostle/681Ph", "https://api.scryfall.com/cards/sld/681Φ/"); + put("SLD/Toxin Sliver/635Ph", "https://api.scryfall.com/cards/sld/635Φ/"); + put("SLD/Shattergang Brothers/1315*", "https://api.scryfall.com/cards/sld/1315★/"); + put("SLD/Virulent Sliver/659Ph", "https://api.scryfall.com/cards/sld/659Φ/"); + // CALC - custom alchemy version of cards. put("CALC/C-Pillar of the Paruns", "https://api.scryfall.com/cards/dis/176/"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 04d92f098fe..c22d0297ec1 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -1275,9 +1275,9 @@ public class ScryfallImageSupportTokens { put("DDE/Saproling", "https://api.scryfall.com/cards/tdde/3/en?format=image"); // DDD - put("DDD/Beast/1", "https://api.scryfall.com/cards/tddd/1/en?format=image"); - put("DDD/Beast/2", "https://api.scryfall.com/cards/tddd/2/en?format=image"); - put("DDD/Elephant", "https://api.scryfall.com/cards/tddd/3/en?format=image"); + put("DDD/Beast/1", "https://api.scryfall.com/cards/tddd/T1/en?format=image"); + put("DDD/Beast/2", "https://api.scryfall.com/cards/tddd/T2/en?format=image"); + put("DDD/Elephant", "https://api.scryfall.com/cards/tddd/T3/en?format=image"); // SOM put("SOM/Cat", "https://api.scryfall.com/cards/tsom/1/en?format=image"); @@ -2189,6 +2189,95 @@ public class ScryfallImageSupportTokens { put("LCI/Vampire", "https://api.scryfall.com/cards/tlci/4/en?format=image"); put("LCI/Vampire Demon", "https://api.scryfall.com/cards/tlci/15/en?format=image"); + // LCC + put("LCC/Beast", "https://api.scryfall.com/cards/tlcc/8/en?format=image"); + put("LCC/Bird", "https://api.scryfall.com/cards/tlcc/2/en?format=image"); + put("LCC/Blood", "https://api.scryfall.com/cards/tlcc/15/en?format=image"); + put("LCC/Boar", "https://api.scryfall.com/cards/tlcc/9/en?format=image"); + put("LCC/Dinosaur", "https://api.scryfall.com/cards/tlcc/10/en?format=image"); + put("LCC/Dinosaur Beast", "https://api.scryfall.com/cards/tlcc/11/en?format=image"); + put("LCC/Elephant", "https://api.scryfall.com/cards/tlcc/12/en?format=image"); + put("LCC/Frog Lizard", "https://api.scryfall.com/cards/tlcc/13/en?format=image"); + put("LCC/Merfolk", "https://api.scryfall.com/cards/tlcc/3/en?format=image"); + put("LCC/Pirate", "https://api.scryfall.com/cards/tlcc/5/en?format=image"); + put("LCC/Ragavan", "https://api.scryfall.com/cards/tlcc/7/en?format=image"); + put("LCC/Salamander Warrior", "https://api.scryfall.com/cards/tlcc/4/en?format=image"); + put("LCC/Shapeshifter", "https://api.scryfall.com/cards/tlcc/1/en?format=image"); + put("LCC/Emblem Sorin", "https://api.scryfall.com/cards/tlcc/16/en?format=image"); + put("LCC/Vampire/1", "https://api.scryfall.com/cards/tlcc/6/en?format=image"); + put("LCC/Vampire/2", "https://api.scryfall.com/cards/tlcc/14/en?format=image"); + + // RVR + put("RVR/Angel/1", "https://api.scryfall.com/cards/trvr/2/en?format=image"); + put("RVR/Angel/2", "https://api.scryfall.com/cards/trvr/3/en?format=image"); + put("RVR/Beast", "https://api.scryfall.com/cards/trvr/14/en?format=image"); + put("RVR/Bird", "https://api.scryfall.com/cards/trvr/1/en?format=image"); + put("RVR/Bird Illusion", "https://api.scryfall.com/cards/trvr/5/en?format=image"); + put("RVR/Centaur", "https://api.scryfall.com/cards/trvr/10/en?format=image"); + put("RVR/Emblem Domri", "https://api.scryfall.com/cards/trvr/20/en?format=image"); + put("RVR/Dragon", "https://api.scryfall.com/cards/trvr/7/en?format=image"); + put("RVR/Elf Knight", "https://api.scryfall.com/cards/trvr/15/en?format=image"); + put("RVR/Goblin/1", "https://api.scryfall.com/cards/trvr/8/en?format=image"); + put("RVR/Goblin/2", "https://api.scryfall.com/cards/trvr/9/en?format=image"); + put("RVR/Rhino", "https://api.scryfall.com/cards/trvr/11/en?format=image"); + put("RVR/Saproling", "https://api.scryfall.com/cards/trvr/12/en?format=image"); + put("RVR/Soldier", "https://api.scryfall.com/cards/trvr/16/en?format=image"); + put("RVR/Sphinx", "https://api.scryfall.com/cards/trvr/17/en?format=image"); + put("RVR/Spirit/1", "https://api.scryfall.com/cards/trvr/4/en?format=image"); + put("RVR/Spirit/2", "https://api.scryfall.com/cards/trvr/18/en?format=image"); + put("RVR/Voja", "https://api.scryfall.com/cards/trvr/19/en?format=image"); + put("RVR/Wurm", "https://api.scryfall.com/cards/trvr/13/en?format=image"); + put("RVR/Zombie", "https://api.scryfall.com/cards/trvr/6/en?format=image"); + + // MKM + put("MKM/Bat", "https://api.scryfall.com/cards/tmkm/4/en?format=image"); + put("MKM/Clue/1", "https://api.scryfall.com/cards/tmkm/14/en?format=image"); + put("MKM/Clue/2", "https://api.scryfall.com/cards/tmkm/15/en?format=image"); + put("MKM/Clue/3", "https://api.scryfall.com/cards/tmkm/16/en?format=image"); + put("MKM/Clue/4", "https://api.scryfall.com/cards/tmkm/17/en?format=image"); + put("MKM/Clue/5", "https://api.scryfall.com/cards/tmkm/18/en?format=image"); + put("MKM/Detective", "https://api.scryfall.com/cards/tmkm/10/en?format=image"); + put("MKM/Dog", "https://api.scryfall.com/cards/tmkm/1/en?format=image"); + put("MKM/Goblin", "https://api.scryfall.com/cards/tmkm/6/en?format=image"); + put("MKM/Human", "https://api.scryfall.com/cards/tmkm/2/en?format=image"); + put("MKM/Imp", "https://api.scryfall.com/cards/tmkm/7/en?format=image"); + put("MKM/Merfolk", "https://api.scryfall.com/cards/tmkm/3/en?format=image"); + put("MKM/Ooze", "https://api.scryfall.com/cards/tmkm/8/en?format=image"); + put("MKM/Plant", "https://api.scryfall.com/cards/tmkm/9/en?format=image"); + put("MKM/Skeleton", "https://api.scryfall.com/cards/tmkm/5/en?format=image"); + put("MKM/Spider", "https://api.scryfall.com/cards/tmkm/11/en?format=image"); + put("MKM/Spirit", "https://api.scryfall.com/cards/tmkm/12/en?format=image"); + put("MKM/Thopter/1", "https://api.scryfall.com/cards/tmkm/19/en?format=image"); + put("MKM/Thopter/2", "https://api.scryfall.com/cards/tmkm/20/en?format=image"); + put("MKM/Voja Fenstalker", "https://api.scryfall.com/cards/tmkm/13/en?format=image"); + + // MKC + put("MKC/Cat", "https://api.scryfall.com/cards/tmkc/15/en?format=image"); + put("MKC/Clue", "https://api.scryfall.com/cards/tmkc/22/en?format=image"); + put("MKC/Construct", "https://api.scryfall.com/cards/tmkc/23/en?format=image"); + put("MKC/Drake", "https://api.scryfall.com/cards/tmkc/6/en?format=image"); + put("MKC/Eldrazi", "https://api.scryfall.com/cards/tmkc/1/en?format=image"); + put("MKC/Food", "https://api.scryfall.com/cards/tmkc/24/en?format=image"); + put("MKC/Gold", "https://api.scryfall.com/cards/tmkc/25/en?format=image"); + put("MKC/Human Soldier", "https://api.scryfall.com/cards/tmkc/2/en?format=image"); + put("MKC/Insect/1", "https://api.scryfall.com/cards/tmkc/16/en?format=image"); + put("MKC/Insect/2", "https://api.scryfall.com/cards/tmkc/17/en?format=image"); + put("MKC/Kobolds of Kher Keep", "https://api.scryfall.com/cards/tmkc/12/en?format=image"); + put("MKC/Koma's Coil", "https://api.scryfall.com/cards/tmkc/7/en?format=image"); + put("MKC/Lightning Rager", "https://api.scryfall.com/cards/tmkc/13/en?format=image"); + put("MKC/Ogre", "https://api.scryfall.com/cards/tmkc/14/en?format=image"); + put("MKC/Phyrexian Germ", "https://api.scryfall.com/cards/tmkc/10/en?format=image"); + put("MKC/Rhino Warrior", "https://api.scryfall.com/cards/tmkc/18/en?format=image"); + put("MKC/Salamander Warrior", "https://api.scryfall.com/cards/tmkc/8/en?format=image"); + put("MKC/Saproling", "https://api.scryfall.com/cards/tmkc/19/en?format=image"); + put("MKC/Snake", "https://api.scryfall.com/cards/tmkc/20/en?format=image"); + put("MKC/Soldier", "https://api.scryfall.com/cards/tmkc/3/en?format=image"); + put("MKC/Spirit", "https://api.scryfall.com/cards/tmkc/4/en?format=image"); + put("MKC/Tentacle", "https://api.scryfall.com/cards/tmkc/9/en?format=image"); + put("MKC/Tiny", "https://api.scryfall.com/cards/tmkc/21/en?format=image"); + put("MKC/Treasure", "https://api.scryfall.com/cards/tmkc/26/en?format=image"); + put("MKC/Zombie", "https://api.scryfall.com/cards/tmkc/11/en?format=image"); + // generate supported sets supportedSets.clear(); for (String cardName : this.keySet()) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java index c1dadc8f1a9..5ad0dbffd2f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java @@ -1,18 +1,19 @@ package org.mage.plugins.card.dl.sources; +import org.mage.plugins.card.dl.DownloadJob; +import org.mage.plugins.card.utils.CardImageUtils; + import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.mage.plugins.card.dl.DownloadJob; -import org.mage.plugins.card.utils.CardImageUtils; - - import static org.mage.card.arcane.ManaSymbols.getSymbolFileNameAsSVG; import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; @@ -105,7 +106,7 @@ public class ScryfallSymbolsSource implements Iterable { if (destFile.exists() && (destFile.length() > 0)) { continue; } - try(FileOutputStream stream = new FileOutputStream(destFile)) { + try (FileOutputStream stream = new FileOutputStream(destFile)) { // base64 transform String data64 = foundedData.get(searchCode); Base64.Decoder dec = Base64.getDecoder(); @@ -166,16 +167,14 @@ public class ScryfallSymbolsSource implements Iterable { } } - private String destFile = ""; - public ScryfallSymbolsDownloadJob() { // download init - super("Scryfall symbols source", fromURL(""), toFile(DOWNLOAD_TEMP_FILE)); // url setup on preparing stage - this.destFile = DOWNLOAD_TEMP_FILE; - this.addPropertyChangeListener(STATE_PROP_NAME, new ScryfallDownloadOnFinishedListener(this.destFile)); + super("Scryfall symbols source", fromURL(""), toFile(DOWNLOAD_TEMP_FILE), true); // url setup on preparing stage + String destFile = DOWNLOAD_TEMP_FILE; + this.addPropertyChangeListener(STATE_PROP_NAME, new ScryfallDownloadOnFinishedListener(destFile)); - // clear dest file (always download new data) - File file = new File(this.destFile); + // duplicate a forceToDownload param above, but it's ok to clear temp file anyway + File file = new File(destFile); if (file.exists()) { file.delete(); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java index 6e442633ab1..5149c81eefe 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java @@ -11,63 +11,34 @@ public class CardDownloadData { private String name; private String downloadName; - private String fileName = ""; private String set; private final String collectorId; private final Integer imageNumber; - private boolean token; - private final boolean twoFacedCard; - private final boolean secondSide; - private boolean flipCard; - private boolean flippedSide; - private boolean splitCard; - private final boolean usesVariousArt; - private String tokenClassName; + private boolean isToken; + private boolean isSecondSide; + private boolean isFlippedSide; + private boolean isSplitCard; + private final boolean isUsesVariousArt; - public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber) { - this(name, setCode, collectorId, usesVariousArt, imageNumber, false, ""); - } - - public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token) { - this(name, setCode, collectorId, usesVariousArt, imageNumber, token, false, false, ""); - } - - public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token, String fileName) { - this(name, setCode, collectorId, usesVariousArt, imageNumber, token, false, false, ""); - this.fileName = fileName; - } - - public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token, boolean twoFacedCard, boolean secondSide) { - this(name, setCode, collectorId, usesVariousArt, imageNumber, token, twoFacedCard, secondSide, ""); - } - - public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token, boolean twoFacedCard, boolean secondSide, String tokenClassName) { + public CardDownloadData(String name, String setCode, String collectorId, boolean isUsesVariousArt, Integer imageNumber) { this.name = name; this.set = setCode; this.collectorId = collectorId; - this.usesVariousArt = usesVariousArt; + this.isUsesVariousArt = isUsesVariousArt; this.imageNumber = imageNumber; - this.token = token; - this.twoFacedCard = twoFacedCard; - this.secondSide = secondSide; - this.tokenClassName = tokenClassName; } public CardDownloadData(final CardDownloadData card) { this.name = card.name; this.downloadName = card.downloadName; - this.fileName = card.fileName; this.set = card.set; this.collectorId = card.collectorId; this.imageNumber = card.imageNumber; - this.token = card.token; - this.twoFacedCard = card.twoFacedCard; - this.secondSide = card.secondSide; - this.flipCard = card.flipCard; - this.flippedSide = card.flippedSide; - this.splitCard = card.splitCard; - this.usesVariousArt = card.usesVariousArt; - this.tokenClassName = card.tokenClassName; + this.isToken = card.isToken; + this.isSecondSide = card.isSecondSide; + this.isFlippedSide = card.isFlippedSide; + this.isSplitCard = card.isSplitCard; + this.isUsesVariousArt = card.isUsesVariousArt; } @Override @@ -88,14 +59,11 @@ public class CardDownloadData { if (!Objects.equals(this.collectorId, other.collectorId)) { return false; } - if (this.token != other.token) { - return false; - } - if (this.twoFacedCard != other.twoFacedCard) { + if (this.isToken != other.isToken) { return false; } - return this.secondSide == other.secondSide; + return this.isSecondSide == other.isSecondSide; } @Override @@ -105,9 +73,8 @@ public class CardDownloadData { hash = 47 * hash + (this.set != null ? this.set.hashCode() : 0); hash = 47 * hash + (this.collectorId != null ? this.collectorId.hashCode() : 0); hash = 47 * hash + (this.imageNumber != null ? this.imageNumber.hashCode() : 0); - hash = 47 * hash + (this.token ? 1 : 0); - hash = 47 * hash + (this.twoFacedCard ? 1 : 0); - hash = 47 * hash + (this.secondSide ? 1 : 0); + hash = 47 * hash + (this.isToken ? 1 : 0); + hash = 47 * hash + (this.isSecondSide ? 1 : 0); return hash; } @@ -144,14 +111,6 @@ public class CardDownloadData { return name; } - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - public void setName(String name) { this.name = name; } @@ -164,28 +123,20 @@ public class CardDownloadData { this.set = set; } - public void setTokenClassName(String tokenClassName) { - this.tokenClassName = tokenClassName; - } - - public String getAffectedClassName() { - return tokenClassName.isEmpty() ? name.replaceAll("[^a-zA-Z0-9]", "") : tokenClassName; - } - public boolean isToken() { - return token; + return isToken; } public void setToken(boolean token) { - this.token = token; - } - - public boolean isTwoFacedCard() { - return twoFacedCard; + this.isToken = token; } public boolean isSecondSide() { - return secondSide; + return isSecondSide; + } + + public void setSecondSide(boolean isSecondSide) { + this.isSecondSide = isSecondSide; } public String getDownloadName() { @@ -196,20 +147,12 @@ public class CardDownloadData { this.downloadName = downloadName; } - public boolean isFlipCard() { - return flipCard; - } - - public void setFlipCard(boolean flipCard) { - this.flipCard = flipCard; - } - public boolean isSplitCard() { - return splitCard; + return isSplitCard; } public void setSplitCard(boolean splitCard) { - this.splitCard = splitCard; + this.isSplitCard = splitCard; } public Integer getImageNumber() { @@ -217,14 +160,14 @@ public class CardDownloadData { } public boolean getUsesVariousArt() { - return usesVariousArt; + return isUsesVariousArt; } public boolean isFlippedSide() { - return flippedSide; + return isFlippedSide; } public void setFlippedSide(boolean flippedSide) { - this.flippedSide = flippedSide; + this.isFlippedSide = flippedSide; } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 8c48e87fa46..1d5be220e82 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -10,6 +10,8 @@ import mage.client.MageFrame; import mage.client.dialog.DownloadImagesDialog; import mage.client.dialog.PreferencesDialog; import mage.client.util.CardLanguage; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageCaches; import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; import net.java.truevfs.access.TFile; @@ -437,14 +439,19 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements && !"0".equals(card.getCardNumber()) && !card.getSetCode().isEmpty()) { String cardName = card.getName(); - CardDownloadData url = new CardDownloadData(cardName, card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, false, card.isDoubleFaced(), card.isNightCard()); + CardDownloadData url = new CardDownloadData( + cardName, + card.getSetCode(), + card.getCardNumber(), + card.usesVariousArt(), + 0); + url.setSecondSide(card.isNightCard()); // variations must have diff file names with additional postfix if (url.getUsesVariousArt()) { url.setDownloadName(createDownloadName(card)); } - url.setFlipCard(card.isFlipCard()); url.setSplitCard(card.isSplitCard()); // main side @@ -467,7 +474,9 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements card.getSetCode(), secondSideCard.getCardNumber(), card.usesVariousArt(), - 0, false, card.isDoubleFaced(), true); + 0 + ); + url.setSecondSide(true); allCardsUrls.add(url); } if (card.isFlipCard()) { @@ -479,9 +488,10 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), - 0, false, card.isDoubleFaced(), card.isNightCard()); - cardDownloadData.setFlipCard(true); + 0 + ); cardDownloadData.setFlippedSide(true); + cardDownloadData.setSecondSide(card.isNightCard()); allCardsUrls.add(cardDownloadData); } if (card.getMeldsToCardName() != null) { @@ -500,7 +510,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements card.getSetCode(), meldsToCard.getCardNumber(), card.usesVariousArt(), - 0, false, false, false); + 0 + ); allCardsUrls.add(url); } if (card.isModalDoubleFacedCard()) { @@ -512,7 +523,9 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), - 0, false, true, true); + 0 + ); + cardDownloadData.setSecondSide(true); allCardsUrls.add(cardDownloadData); } } else if (card.getCardNumber().isEmpty() || "0".equals(card.getCardNumber())) { @@ -531,10 +544,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements token.getSetCode(), "0", false, - token.getImageNumber(), - true, - token.getImageFileName() - ); + token.getImageNumber()); + card.setToken(true); allCardsUrls.add(card); }); } catch (Exception e) { @@ -675,8 +686,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements reloadCardsToDownload(uiDialog.getSetsCombo().getSelectedItem().toString()); enableDialogButtons(); - // reset images cache - ImageCache.clearCache(); + // reset GUI and cards to use new images + GUISizeHelper.refreshGUIAndCards(); } static String convertStreamToString(InputStream is) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index ab6d26c2faf..9518461689b 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -1,14 +1,15 @@ package org.mage.plugins.card.images; -import com.google.common.collect.ComputationException; import mage.abilities.icon.CardIconColor; import mage.client.constants.Constants; +import mage.client.util.ImageCaches; import mage.client.util.SoftValuesLoadingCache; import mage.client.util.TransformedImageCache; import mage.view.CardView; import net.java.truevfs.access.TFile; import net.java.truevfs.access.TFileInputStream; import org.apache.log4j.Logger; +import org.mage.card.arcane.CardPanelRenderModeImage; import org.mage.plugins.card.dl.sources.DirectLinksForDownload; import org.mage.plugins.card.utils.CardImageUtils; import org.mage.plugins.card.utils.impl.ImageManagerImpl; @@ -21,17 +22,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * This class stores ALL card images in a cache with soft values. this means + * This class stores ALL card images in a cache with soft values. This means * that the images may be garbage collected when they are not needed any more, * but will be kept as long as possible. - *

- * Key format: "[cardname]#[setname]#[type]#[collectorID]#[image size]#[additional data]" - * - *

  • #Normal: request for unrotated image
  • - *
  • #Tapped: request for rotated image
  • - *
  • #Cropped: request for cropped image that is used for Shandalar like card - * look
  • - * * * @author JayDi85 */ @@ -39,195 +32,93 @@ public final class ImageCache { private static final Logger LOGGER = Logger.getLogger(ImageCache.class); - private static final SoftValuesLoadingCache IMAGE_CACHE; // cards and tokens - private static final SoftValuesLoadingCache FACE_IMAGE_CACHE; - private static final SoftValuesLoadingCache CARD_ICONS_CACHE; + // global cache for both mtgo and image render modes + private static final SoftValuesLoadingCache SHARED_CARD_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(ImageCache::createCardOrTokenImage)); + private static final SoftValuesLoadingCache SHARED_CARD_ICONS_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(ImageCache::createIcon)); - /** - * Common pattern for keys. See ImageCache.getKey for structure info - */ - private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)"); + // format: name #setcode #imagenumber #cardnumber #size #usesVariousArt + private static final Pattern CARD_IMAGE_KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)"); + + // format: size #icon #color private static final Pattern CARD_ICON_KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)"); - static { - // softValues() = Specifies that each value (not key) stored in the map should be wrapped in a SoftReference - // (by default, strong references are used). Softly-referenced objects will be garbage-collected in a - // globally least-recently-used manner, in response to memory demand. - IMAGE_CACHE = SoftValuesLoadingCache.from(key -> { - try { - boolean usesVariousArt = false; - if (key.matches(".*#usesVariousArt.*")) { - usesVariousArt = true; - key = key.replace("#usesVariousArt", ""); - } - Matcher m = KEY_PATTERN.matcher(key); - - if (m.matches()) { - String name = m.group(1); - String setCode = m.group(2); - Integer type = Integer.parseInt(m.group(3)); - String collectorId = m.group(4); - if (collectorId.equals("null")) { - collectorId = "0"; - } - - CardDownloadData info = new CardDownloadData(name, setCode, collectorId, usesVariousArt, type); - - boolean cardback = false; - String path; - if (collectorId.isEmpty() || "0".equals(collectorId)) { - // TOKEN - // can be a normal token or a token from card (see embalm ability) - info.setToken(true); - TFile tokenFile; - - // try normal token - path = CardImageUtils.buildImagePathToCardOrToken(info); - tokenFile = getTFile(path); - - // try token from card - // TODO: return image from another set on empty image? - if (tokenFile == null || !tokenFile.exists()) { - CardDownloadData tempInfo = new CardDownloadData(info); - tempInfo.setToken(false); - path = CardImageUtils.buildImagePathToCardOrToken(info); - tokenFile = getTFile(path); - } - - // try unknown token image - if (tokenFile == null || !tokenFile.exists()) { - // TODO: replace empty token by other default card, not cardback - cardback = true; - path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename); - } - } else { - // CARD - path = CardImageUtils.buildImagePathToCardOrToken(info); - } - - TFile file = getTFile(path); - if (file == null) { - return new ImageCacheData(path, null); - } - - if (cardback) { - // TODO: is there any different in images styles? Cardback must be from scryfall, not wizards - // need cardback image - BufferedImage image = loadImage(file); - image = getRoundCorner(image); - return new ImageCacheData(path, image); - } else { - // need normal card image - BufferedImage image = loadImage(file); - image = getWizardsCard(image); - image = getRoundCorner(image); - return new ImageCacheData(path, image); - } - } else { - throw new RuntimeException( - "Requested image doesn't fit the requirement for key (##): " + key); - } - } catch (Exception ex) { - if (ex instanceof ComputationException) { - throw (ComputationException) ex; - } else { - throw new ComputationException(ex); - } - } - }); - - FACE_IMAGE_CACHE = SoftValuesLoadingCache.from(key -> { - try { - Matcher m = KEY_PATTERN.matcher(key); - - if (m.matches()) { - String name = m.group(1); - String setCode = m.group(2); - // skip type - // skip collectorId - - String path = CardImageUtils.generateFaceImagePath(name, setCode); - TFile file = getTFile(path); - if (file == null) { - return new ImageCacheData(path, null); - } - - BufferedImage image = loadImage(file); - return new ImageCacheData(path, image); - } else { - throw new RuntimeException( - "Requested face image doesn't fit the requirement for key (##: " + key); - } - } catch (Exception ex) { - if (ex instanceof ComputationException) { - throw (ComputationException) ex; - } else { - throw new ComputationException(ex); - } - } - }); - - CARD_ICONS_CACHE = SoftValuesLoadingCache.from(key -> { - try { - Matcher m = CARD_ICON_KEY_PATTERN.matcher(key); - - if (m.matches()) { - int cardSize = Integer.parseInt(m.group(1)); - String resourceName = m.group(2); - CardIconColor cardIconColor = CardIconColor.valueOf(m.group(3)); - BufferedImage image = ImageManagerImpl.instance.getCardIcon(resourceName, cardSize, cardIconColor); - return new ImageCacheData(resourceName, image); - } else { - throw new RuntimeException("Wrong card icons image key format: " + key); - } - } catch (Exception ex) { - if (ex instanceof ComputationException) { - throw (ComputationException) ex; - } else { - throw new ComputationException(ex); - } - } - }); - } - - public static void clearCache() { - IMAGE_CACHE.invalidateAll(); - FACE_IMAGE_CACHE.invalidateAll(); - CARD_ICONS_CACHE.invalidateAll(); - } - private ImageCache() { } - public static ImageCacheData getCardbackImage() { - String path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename); - BufferedImage image = ImageCache.loadImage(getTFile(path)); - image = getRoundCorner(image); - return new ImageCacheData(path, image); + private static ImageCacheData createCardOrTokenImage(String key) { + boolean usesVariousArt = false; + if (key.matches(".*#usesVariousArt.*")) { + usesVariousArt = true; + key = key.replace("#usesVariousArt", ""); + } + Matcher m = CARD_IMAGE_KEY_PATTERN.matcher(key); + + if (m.matches()) { + String name = m.group(1); + String setCode = m.group(2); + Integer imageNumber = Integer.parseInt(m.group(3)); + String collectorId = m.group(4); + if (collectorId.equals("null")) { + collectorId = "0"; + } + + CardDownloadData info = new CardDownloadData(name, setCode, collectorId, usesVariousArt, imageNumber); + + String path; + if (collectorId.isEmpty() || "0".equals(collectorId)) { + // TOKEN + // can be a normal token or a token from card (see embalm ability) + info.setToken(true); + TFile tokenFile; + + // try normal token + path = CardImageUtils.buildImagePathToCardOrToken(info); + tokenFile = getTFile(path); + + // try token from card + // TODO: unused code? + // TODO: return image from another set on empty image? + if (tokenFile == null || !tokenFile.exists()) { + CardDownloadData tempInfo = new CardDownloadData(info); + tempInfo.setToken(false); + path = CardImageUtils.buildImagePathToCardOrToken(info); + tokenFile = getTFile(path); + } + + // try unknown token image + if (tokenFile == null || !tokenFile.exists()) { + // TODO: replace empty token by other default card, not cardback + path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename); + } + } else { + // CARD + path = CardImageUtils.buildImagePathToCardOrToken(info); + } + + TFile file = getTFile(path); + if (file == null) { + return new ImageCacheData(path, null); + } + + BufferedImage image = loadImage(file); + image = getRoundCorner(image); + return new ImageCacheData(path, image); + } else { + throw new IllegalArgumentException("Unknown card image's key format: " + key); + } } - public static ImageCacheData getMorphImage() { - // TODO: replace by downloadable morth image - CardDownloadData info = new CardDownloadData("Morph", "KTK", "0", false, 0); - info.setToken(true); - String path = CardImageUtils.buildImagePathToCardOrToken(info); - - TFile file = getTFile(path); - BufferedImage image = loadImage(file); - image = getRoundCorner(image); - return new ImageCacheData(path, image); - } - - public static ImageCacheData getManifestImage() { - // TODO: replace by downloadable manifestest image - CardDownloadData info = new CardDownloadData("Manifest", "FRF", "0", false, 0); - info.setToken(true); - String path = CardImageUtils.buildImagePathToCardOrToken(info); - - TFile file = getTFile(path); - BufferedImage image = loadImage(file); - image = getRoundCorner(image); - return new ImageCacheData(path, image); + private static ImageCacheData createIcon(String key) { + Matcher m = CARD_ICON_KEY_PATTERN.matcher(key); + if (m.matches()) { + int cardSize = Integer.parseInt(m.group(1)); + String resourceName = m.group(2); + CardIconColor cardIconColor = CardIconColor.valueOf(m.group(3)); + BufferedImage image = ImageManagerImpl.instance.getCardIcon(resourceName, cardSize, cardIconColor); + return new ImageCacheData(resourceName, image); + } else { + throw new IllegalArgumentException("Unknown card icon's key format: " + key); + } } public static BufferedImage getRoundCorner(BufferedImage image) { @@ -269,61 +160,52 @@ public final class ImageCache { } } - public static ImageCacheData getImageOriginal(CardView card) { - return getImage(getKey(card, card.getName(), 0)); + /** Find image for current side + */ + public static ImageCacheData getCardImageOriginal(CardView card) { + return getCardImage(getKey(card, card.getName(), 0)); } - public static ImageCacheData getImageOriginalAlternateName(CardView card) { - return getImage(getKey(card, card.getAlternateName(), 0)); + /** + * Find image for other side + */ + public static ImageCacheData getCardImageAlternate(CardView card) { + return getCardImage(getKey(card, card.getAlternateName(), 0)); } public static ImageCacheData getCardIconImage(String resourceName, int iconSize, String cardColorName) { return getCardIconImage(getCardIconKey(resourceName, iconSize, cardColorName)); } - /** - * Returns the Image corresponding to the key - */ - private static ImageCacheData getImage(String key) { + private static ImageCacheData getCardImage(String key) { try { - ImageCacheData data = IMAGE_CACHE.getOrNull(key); + ImageCacheData data = SHARED_CARD_IMAGES_CACHE.getOrNull(key); return data != null ? data : new ImageCacheData("ERROR: key - " + key, null); - } catch (ComputationException ex) { - // too low memory - if (ex.getCause() instanceof NullPointerException) { - return new ImageCacheData("ERROR: low memory?", null); + } catch (Exception e) { + if (e.getCause() instanceof NullPointerException) { + // low memory error??? + return new ImageCacheData("ERROR: possible low memory", null); + } else { + // other error + LOGGER.error("Error while loading card image: " + e, e); + return new ImageCacheData("ERROR: see client logs for details", null); } - LOGGER.error(ex, ex); - return new ImageCacheData("ERROR: see logs", null); - } - } - - /** - * Returns the Image corresponding to the key - */ - private static ImageCacheData getFaceImage(String key) { - try { - ImageCacheData data = FACE_IMAGE_CACHE.getOrNull(key); - return data != null ? data : new ImageCacheData("ERROR: key " + key, null); - } catch (ComputationException ex) { - if (ex.getCause() instanceof NullPointerException) { - return new ImageCacheData("ERROR: low memory?", null); - } - LOGGER.error(ex, ex); - return new ImageCacheData("ERROR: see logs", null); } } private static ImageCacheData getCardIconImage(String key) { try { - ImageCacheData data = CARD_ICONS_CACHE.getOrNull(key); + ImageCacheData data = SHARED_CARD_ICONS_CACHE.getOrNull(key); return data != null ? data : new ImageCacheData("ERROR: key - " + key, null); - } catch (ComputationException ex) { - if (ex.getCause() instanceof NullPointerException) { - return new ImageCacheData("ERROR: low memory?", null); + } catch (Exception e) { + if (e.getCause() instanceof NullPointerException) { + // low memory error??? + return new ImageCacheData("ERROR: possible low memory", null); + } else { + // other error + LOGGER.error("Error while loading card icon: " + e, e); + return new ImageCacheData("ERROR: see client logs for details", null); } - LOGGER.error(ex, ex); - return new ImageCacheData("ERROR: see logs", null); } } @@ -332,7 +214,7 @@ public final class ImageCache { * the cache. */ private static ImageCacheData tryGetImage(String key) { - return IMAGE_CACHE.peekIfPresent(key); + return SHARED_CARD_IMAGES_CACHE.peekIfPresent(key); } /** @@ -343,7 +225,11 @@ public final class ImageCache { * @param imageSize - size info, 0 to use original image (with max size) */ private static String getKey(CardView card, String cardName, int imageSize) { - return (card.isToken() ? cardName.replace(" Token", "") : cardName) + String imageFileName = card.getImageFileName(); + if (imageFileName.isEmpty()) { + imageFileName = cardName; + } + return imageFileName.replace(" Token", "") + '#' + card.getExpansionSetCode() + '#' + card.getImageNumber() + '#' + card.getCardNumber() @@ -420,9 +306,9 @@ public final class ImageCache { * @param height * @return */ - public static ImageCacheData getImage(CardView card, int width, int height) { + public static ImageCacheData getCardImage(CardView card, int width, int height) { String key = getKey(card, card.getName(), width); - ImageCacheData data = getImage(key); + ImageCacheData data = getCardImage(key); if (data.getImage() == null) { LOGGER.debug("Image doesn't exists in the cache: " + key); return data; @@ -438,23 +324,6 @@ public final class ImageCache { return data; } - /** - * Returns the image appropriate to display the card in the picture panel - * - * @param card - * @param width - * @param height - * @return - */ - public static ImageCacheData getFaceImage(CardView card, int width, int height) { - String key = getFaceKey(card, card.getName(), card.getExpansionSetCode()); - ImageCacheData data = getFaceImage(key); - if (data.getImage() == null) { - LOGGER.debug(key + " (faceimage) not found"); - } - return data; - } - /** * Returns the image appropriate to display for a card in a picture panel, * but only it was ALREADY LOADED. That is, the call is immediate and will diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java index e3a990156bf..b53360efe0e 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java @@ -1,10 +1,14 @@ package org.mage.plugins.card.utils; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.cards.repository.TokenRepository; import mage.client.MageFrame; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; import mage.remote.Connection; import mage.remote.Connection.ProxyType; +import mage.view.CardView; import org.apache.log4j.Logger; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -113,7 +117,6 @@ public final class CardImageUtils { } public static String buildImagePathToCardOrToken(CardDownloadData card) { - String setPath = buildImagePathToSet(card); String prefixType = ""; @@ -121,10 +124,7 @@ public final class CardImageUtils { prefixType = " " + card.getImageNumber(); } - String cardName = card.getFileName(); - if (cardName.isEmpty()) { - cardName = prepareCardNameForFile(card.getName()); - } + String cardName = prepareCardNameForFile(card.getName()); String finalFileName; if (card.getUsesVariousArt()) { @@ -138,6 +138,59 @@ public final class CardImageUtils { return setPath + finalFileName; } + /** + * Special version for CardView and direct images info + * (real card images uses image cache and key logic, see ImageCache.getKey) + * + * @return relative image path or "ERROR + reason" + */ + public static String buildImagePathToCardView(CardView card) { + String imageFile; + String imageFileName = card.getImageFileName(); + if (imageFileName.isEmpty()) { + imageFileName = card.getName(); + } + + if (imageFileName.isEmpty()) { + return "ERROR: empty image file name, object type - " + card.getMageObjectType(); + } + + if (card.getMageObjectType().isUseTokensRepository() + || card.getExpansionSetCode().equals(TokenRepository.XMAGE_TOKENS_SET_CODE)) { + // token images or inner cards like face down + CardDownloadData cardData = new CardDownloadData( + imageFileName.replace(" Token", ""), + card.getExpansionSetCode(), + card.getCardNumber(), + card.getUsesVariousArt(), + card.getImageNumber()); + cardData.setToken(true); + imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData); + } else { + // card images + // workaround to find various art settings first + // TODO: no needs in workaround?! ?! + boolean usesVariousArt = false; + CardInfo cardInfo = CardRepository.instance.findCardWithPreferredSetAndNumber( + card.getName(), + card.getExpansionSetCode(), + card.getCardNumber() + ); + if (cardInfo != null) { + usesVariousArt = cardInfo.usesVariousArt(); + } + CardDownloadData cardData = new CardDownloadData( + imageFileName, + card.getExpansionSetCode(), + card.getCardNumber(), + card.getUsesVariousArt(), // TODO: need to use usesVariousArt instead card? + card.getImageNumber() + ); + imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData); + } + return imageFile; + } + public static String generateFaceImagePath(String cardName, String setCode) { return getImagesDir() + File.separator + "FACE" + File.separator + fixSetNameForWindows(setCode) + File.separator + prepareCardNameForFile(cardName) + ".jpg"; } diff --git a/Mage.Common/src/main/java/mage/utils/SystemUtil.java b/Mage.Common/src/main/java/mage/utils/SystemUtil.java index cda1bffda17..b253196888f 100644 --- a/Mage.Common/src/main/java/mage/utils/SystemUtil.java +++ b/Mage.Common/src/main/java/mage/utils/SystemUtil.java @@ -715,7 +715,7 @@ public final class SystemUtil { Set cardsToLoad = new HashSet<>(); for (int i = 0; i < command.Amount; i++) { CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); - Card card = cardInfo != null ? cardInfo.getCard() : null; + Card card = cardInfo != null ? cardInfo.createCard() : null; if (card != null) { cardsToLoad.add(card); } @@ -783,7 +783,7 @@ public final class SystemUtil { Set cardsToLoad = new LinkedHashSet<>(); for (int i = 0; i < amount; i++) { - cardsToLoad.add(cardInfo.getCard()); + cardsToLoad.add(cardInfo.createCard()); } game.loadCards(cardsToLoad, owner.getId()); @@ -798,7 +798,7 @@ public final class SystemUtil { // TODO: replace by player.move? switch (zone) { case BATTLEFIELD: - CardUtil.putCardOntoBattlefieldWithEffects(source, game, card, player); + CardUtil.putCardOntoBattlefieldWithEffects(source, game, card, player, false); break; case LIBRARY: card.setZone(Zone.LIBRARY, game); diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 619feb2dc10..6d0652df666 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -16,9 +16,12 @@ import mage.abilities.icon.CardIcon; import mage.abilities.icon.CardIconImpl; import mage.abilities.icon.CardIconType; import mage.abilities.keyword.AftermathAbility; +import mage.abilities.keyword.ForetellAbility; import mage.cards.*; import mage.cards.mock.MockCard; import mage.cards.repository.CardInfo; +import mage.cards.repository.TokenInfo; +import mage.cards.repository.TokenRepository; import mage.constants.*; import mage.counters.Counter; import mage.counters.CounterType; @@ -89,7 +92,8 @@ public class CardView extends SimpleCardView { protected boolean isToken; protected CardView ability; - protected int imageNumber; + protected String imageFileName = ""; + protected int imageNumber = 0; protected boolean extraDeckCard; protected boolean transformable; // can toggle one card side to another (transformable cards, modal double faces) @@ -134,12 +138,12 @@ public class CardView extends SimpleCardView { protected List cardIcons = new ArrayList<>(); // additional icons to render // GUI related: additional info about current object (example: real PT) + // warning, do not send full object, use some fields only (client must not get any server side data) + // warning, don't forget to hide it in face down cards (null) protected MageInt originalPower = null; protected MageInt originalToughness = null; - protected FilterMana originalColorIdentity = null; - protected UUID originalId = null; + protected String originalColorIdentity = null; // GUI related info for sorting, searching, etc protected boolean originalIsCopy = false; - protected boolean originalIsCard = false; /** * Non game usage like deck editor @@ -154,6 +158,17 @@ public class CardView extends SimpleCardView { this(card, game, false); } + /** + * @param card + * @param game + * @param showAsControlled is the card view created for the card controller - used + * for morph / face down cards to know which player may see information for + * the card TODO: turn controller can be here too? + */ + public CardView(Card card, Game game, boolean showAsControlled) { + this(card, game, showAsControlled, false); + } + public CardView(Card card, SimpleCardView simpleCardView) { this(card, null, false); this.id = simpleCardView.getId(); @@ -163,11 +178,6 @@ public class CardView extends SimpleCardView { this.isSelected = simpleCardView.isSelected; } - public CardView(Card card, Game game, UUID cardId) { - this(card, game, false); - this.id = cardId; - } - public CardView(final CardView cardView) { super(cardView); @@ -192,6 +202,7 @@ public class CardView extends SimpleCardView { this.expansionSetCode = cardView.expansionSetCode; this.cardNumber = cardView.cardNumber; + this.imageFileName = cardView.imageFileName; this.imageNumber = cardView.imageNumber; this.color = cardView.color.copy(); @@ -246,32 +257,21 @@ public class CardView extends SimpleCardView { this.canAttack = cardView.canAttack; this.canBlock = cardView.canBlock; this.inViewerOnly = cardView.inViewerOnly; - this.originalPower = cardView.originalPower; - this.originalToughness = cardView.originalToughness; - this.originalColorIdentity = cardView.originalColorIdentity; - this.originalId = cardView.originalId; - this.originalIsCard = cardView.originalIsCard; - this.originalIsCopy = cardView.originalIsCopy; + if (cardView.cardIcons != null) { cardView.cardIcons.forEach(icon -> this.cardIcons.add(icon.copy())); } + this.originalPower = cardView.originalPower; + this.originalToughness = cardView.originalToughness; + this.originalColorIdentity = cardView.originalColorIdentity; + this.originalIsCopy = cardView.originalIsCopy; + this.playableStats = cardView.playableStats.copy(); this.isChoosable = cardView.isChoosable; this.isSelected = cardView.isSelected; } - /** - * @param card - * @param game - * @param controlled is the card view created for the card controller - used - * for morph / face down cards to know which player may see information for - * the card - */ - public CardView(Card card, Game game, boolean controlled) { - this(card, game, controlled, false, false); - } - private static String getCardTypeLine(Game game, Card card) { StringBuilder sbType = new StringBuilder(); for (SuperType superType : card.getSuperType(game)) { @@ -290,136 +290,182 @@ public class CardView extends SimpleCardView { } /** - * @param card + * @param sourceCard * @param game - * @param controlled is the card view created for the card controller - used + * @param showAsControlled is the card view created for the card controller/owner - used * for morph / face down cards to know which player may see information for * the card - * @param showFaceDownCard if true and the card is not on the battlefield, - * also a face down card is shown in the view, face down cards will be shown * @param storeZone if true the card zone will be set in the zone attribute. */ - public CardView(Card card, Game game, boolean controlled, boolean showFaceDownCard, boolean storeZone) { - super(card.getId(), card.getExpansionSetCode(), card.getCardNumber(), card.getUsesVariousArt(), game != null); - this.setOriginalValues(card); - this.imageNumber = card.getImageNumber(); + public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean storeZone) { + super(sourceCard.getId(), sourceCard.getExpansionSetCode(), sourceCard.getCardNumber(), sourceCard.getUsesVariousArt(), game != null); + + // TODO: it's too big and can be buggy (something miss?) - must check and refactor: setup face down/up params, setup shared data like counters and targets + + + // Visible logic: + // * Normal card: + // - original name, original image + // * Face down card: + // * my cards or game end: + // - face down status + original name, face down image, day/night button + // * opponent cards: + // - face down status, face down image + + // find real name from original card, cause face down status can be applied to card/spell + String sourceName = sourceCard.getMainCard().getName(); + + // find real spell characteristics before resolve + Card card = sourceCard.copy(); + if (game != null && card instanceof Spell) { + card = ((Spell) card).getSpellAbility().getCharacteristics(game); + } + + // use isFaceDown(game) only here to find real status, all other code must use this.faceDown + this.faceDown = game != null && sourceCard.isFaceDown(game); + boolean showFaceUp = !this.faceDown; + + // show real name and day/night button for controller or any player at the game's end + boolean showHiddenFaceDownData = showAsControlled || (game != null && game.hasEnded()); + + // default image info + this.expansionSetCode = card.getExpansionSetCode(); + this.cardNumber = card.getCardNumber(); + this.imageFileName = card.getImageFileName(); + this.imageNumber = card.getImageNumber(); + this.usesVariousArt = card.getUsesVariousArt(); + + // permanent data + if (showFaceUp) { + this.setOriginalValues(card); + } - // no information available for face down cards as long it's not a controlled face down morph card - // TODO: Better handle this in Framework (but currently I'm not sure how to do it there) LevelX2 - boolean showFaceUp = true; if (game != null) { Zone cardZone = game.getState().getZone(card.getId()); - if (card.isFaceDown(game)) { - showFaceUp = false; - if (Zone.BATTLEFIELD != cardZone) { - if (showFaceDownCard) { - showFaceUp = true; - } - } - } - if (storeZone) { + // TODO: research, why it used here? this.zone = cardZone; } } - // boolean showFaceUp = game == null || !card.isFaceDown(game) || (!game.getState().getZone(card.getId()).equals(Zone.BATTLEFIELD) && showFaceDownCard); + // FACE DOWN if (!showFaceUp) { - this.fillEmpty(card, controlled); - if (card instanceof Spell) { - // TODO: add face down image here??? - // special handling for casting of Morph cards - if (controlled) { - this.name = card.getName(); - this.displayName = card.getName(); - this.displayFullName = card.getName(); - this.alternateName = card.getName(); - } - this.power = "2"; - this.toughness = "2"; - this.rules.add("You may cast this card as a 2/2 face-down creature, with no text," - + " no name, no subtypes, and no mana cost by paying {3} rather than paying its mana cost."); - return; - } else if (card instanceof Permanent) { + this.fillEmptyWithImageInfo(game, card, true); + + // can show face up card name for controller or game end + // TODO: add exception on non empty name of the faced-down card here + String visibleName = CardUtil.getCardNameForGUI(showHiddenFaceDownData ? sourceName : "", this.imageFileName); + this.name = visibleName; + this.displayName = visibleName; + this.displayFullName = visibleName; + this.alternateName = visibleName; + + // TODO: remove workaround - all actual characteristics must get from a card -- same as normal card do + // TODO: must use same code in all zones + // workaround to add PT, creature type and face up ability text (for stack and battlefield zones only) + // in other zones it has only face down status/name + if (sourceCard instanceof Spell + || card instanceof Permanent) { this.power = Integer.toString(card.getPower().getValue()); this.toughness = Integer.toString(card.getToughness().getValue()); - this.cardTypes = new ArrayList<>(card.getCardType(game)); - this.faceDown = card.isFaceDown(game); + this.cardTypes = new ArrayList<>(card.getCardType()); + this.color = card.getColor(null); + this.superTypes = new ArrayList<>(card.getSuperType()); + this.subTypes = card.getSubtype().copy(); + this.rules = new ArrayList<>(card.getRules()); + } + + // GUI: enable day/night button to view original face up card + if (showHiddenFaceDownData) { + this.transformable = true; + this.secondCardFace = new CardView(sourceCard.getMainCard()); // do not use game param, so it will take default card + this.alternateName = sourceCard.getMainCard().getName(); + } + } + + // FACE UP and shared data like counters + + if (showFaceUp) { + SplitCard splitCard = null; + if (card instanceof SplitCard) { + splitCard = (SplitCard) card; + rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH; + } else if (card instanceof Spell) { + switch (card.getSpellAbility().getSpellAbilityType()) { + case SPLIT_FUSED: + splitCard = (SplitCard) ((Spell) card).getCard(); + rotate = true; + break; + case SPLIT_AFTERMATH: + splitCard = (SplitCard) ((Spell) card).getCard(); + rotate = false; + break; + case SPLIT_LEFT: + case SPLIT_RIGHT: + rotate = true; + break; + case MODAL_LEFT: + case MODAL_RIGHT: + rotate = false; + break; + } + } + + String fullCardName; + if (splitCard != null) { + this.isSplitCard = true; + leftSplitName = splitCard.getLeftHalfCard().getName(); + leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols()); + leftSplitRules = splitCard.getLeftHalfCard().getRules(game); + leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard()); + rightSplitName = splitCard.getRightHalfCard().getName(); + rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols()); + rightSplitRules = splitCard.getRightHalfCard().getRules(game); + rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard()); + + fullCardName = card.getName(); // split card contains full name as normal + this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols(); + this.manaCostRightStr = splitCard.getRightHalfCard().getManaCostSymbols(); + } else if (card instanceof ModalDoubleFacedCard) { + this.isModalDoubleFacedCard = true; + ModalDoubleFacedCard mainCard = ((ModalDoubleFacedCard) card); + fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); + this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); + this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); + } else if (card instanceof AdventureCard) { + this.isSplitCard = true; + AdventureCard adventureCard = ((AdventureCard) card); + leftSplitName = adventureCard.getName(); + leftSplitCostsStr = String.join("", adventureCard.getManaCostSymbols()); + leftSplitRules = adventureCard.getSharedRules(game); + leftSplitTypeLine = getCardTypeLine(game, adventureCard); + AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); + rightSplitName = adventureCardSpell.getName(); + rightSplitCostsStr = String.join("", adventureCardSpell.getManaCostSymbols()); + rightSplitRules = adventureCardSpell.getRules(game); + rightSplitTypeLine = getCardTypeLine(game, adventureCardSpell); + fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName(); + this.manaCostLeftStr = adventureCard.getManaCostSymbols(); + this.manaCostRightStr = adventureCardSpell.getManaCostSymbols(); + } else if (card instanceof MockCard) { + // deck editor cards + fullCardName = ((MockCard) card).getFullName(true); + this.manaCostLeftStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.LEFT); + this.manaCostRightStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.RIGHT); } else { - // this.hideInfo = true; - return; + fullCardName = card.getName(); + this.manaCostLeftStr = card.getManaCostSymbols(); + this.manaCostRightStr = new ArrayList<>(); } + + this.name = card.getName(); + this.displayName = card.getName(); + this.displayFullName = fullCardName; + this.rules = new ArrayList<>(card.getRules(game)); + this.manaValue = card.getManaValue(); } - SplitCard splitCard = null; - if (card instanceof SplitCard) { - splitCard = (SplitCard) card; - rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH; - } else if (card instanceof Spell) { - switch (card.getSpellAbility().getSpellAbilityType()) { - case SPLIT_FUSED: - splitCard = (SplitCard) ((Spell) card).getCard(); - rotate = true; - break; - case SPLIT_AFTERMATH: - splitCard = (SplitCard) ((Spell) card).getCard(); - rotate = false; - break; - case SPLIT_LEFT: - case SPLIT_RIGHT: - rotate = true; - break; - case MODAL_LEFT: - case MODAL_RIGHT: - rotate = false; - break; - } - } - - String fullCardName; - if (splitCard != null) { - this.isSplitCard = true; - leftSplitName = splitCard.getLeftHalfCard().getName(); - leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols()); - leftSplitRules = splitCard.getLeftHalfCard().getRules(game); - leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard()); - rightSplitName = splitCard.getRightHalfCard().getName(); - rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols()); - rightSplitRules = splitCard.getRightHalfCard().getRules(game); - rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard()); - - fullCardName = card.getName(); // split card contains full name as normal - this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols(); - this.manaCostRightStr = splitCard.getRightHalfCard().getManaCostSymbols(); - } else if (card instanceof ModalDoubleFacedCard) { - this.isModalDoubleFacedCard = true; - ModalDoubleFacedCard mainCard = ((ModalDoubleFacedCard) card); - fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); - this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); - this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); - } else if (card instanceof AdventureCard) { - AdventureCard adventureCard = ((AdventureCard) card); - AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); - fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName(); - this.manaCostLeftStr = adventureCardSpell.getManaCostSymbols(); - this.manaCostRightStr = adventureCard.getManaCostSymbols(); - } else if (card instanceof MockCard) { - // deck editor cards - fullCardName = ((MockCard) card).getFullName(true); - this.manaCostLeftStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.LEFT); - this.manaCostRightStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.RIGHT); - } else { - fullCardName = card.getName(); - this.manaCostLeftStr = card.getManaCostSymbols(); - this.manaCostRightStr = new ArrayList<>(); - } - - this.name = card.getName(); - this.displayName = card.getName(); - this.displayFullName = fullCardName; - this.rules = new ArrayList<>(card.getRules(game)); - this.manaValue = card.getManaValue(); - + // shared info - counters and other if (card instanceof Permanent) { this.mageObjectType = MageObjectType.PERMANENT; Permanent permanent = (Permanent) card; @@ -457,61 +503,66 @@ public class CardView extends SimpleCardView { } } - this.power = Integer.toString(card.getPower().getValue()); - this.toughness = Integer.toString(card.getToughness().getValue()); - this.cardTypes = new ArrayList<>(card.getCardType(game)); - this.subTypes = card.getSubtype(game).copy(); - this.superTypes = card.getSuperType(game); - this.color = card.getColor(game).copy(); - this.flipCard = card.isFlipCard(); - this.faceDown = !showFaceUp; + // FACE UP INFO + if (showFaceUp) { + // TODO: extract characteristics setup to shared code (same for face down and normal cards) + // PT, card types/subtypes/super/color/rules + this.power = Integer.toString(card.getPower().getValue()); + this.toughness = Integer.toString(card.getToughness().getValue()); + this.cardTypes = new ArrayList<>(card.getCardType(game)); + this.subTypes = card.getSubtype(game).copy(); + this.superTypes = card.getSuperType(game); + this.color = card.getColor(game).copy(); + this.flipCard = card.isFlipCard(); - if (card instanceof PermanentToken) { - this.isToken = true; - this.mageObjectType = MageObjectType.TOKEN; - this.rarity = Rarity.COMMON; - this.rules = new ArrayList<>(card.getRules(game)); - } else { - this.rarity = card.getRarity(); - this.isToken = false; - } - - this.extraDeckCard = card.isExtraDeckCard(); - - // transformable, double faces cards - this.transformable = card.isTransformable(); - - Card secondSideCard = card.getSecondCardFace(); - if (secondSideCard != null) { - this.secondCardFace = new CardView(secondSideCard, game); - this.alternateName = secondCardFace.getName(); - } - - this.flipCard = card.isFlipCard(); - if (card.isFlipCard() && card.getFlipCardName() != null) { - this.alternateName = card.getFlipCardName(); - } - - if (card instanceof ModalDoubleFacedCard) { - this.transformable = true; // enable GUI day/night button - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card; - this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); - this.alternateName = mdfCard.getRightHalfCard().getName(); - } - - Card meldsToCard = card.getMeldsToCard(); - if (meldsToCard != null) { - this.transformable = true; // enable GUI day/night button - this.secondCardFace = new CardView(meldsToCard, game); - this.alternateName = meldsToCard.getName(); - } - - if (card instanceof PermanentToken && card.isTransformable()) { - Token backFace = (Token) ((PermanentToken) card).getOtherFace(); - this.secondCardFace = new CardView(backFace, game); - this.alternateName = backFace.getName(); + if (card instanceof PermanentToken) { + this.isToken = true; + this.mageObjectType = MageObjectType.TOKEN; + this.rarity = Rarity.SPECIAL; + this.rules = new ArrayList<>(card.getRules(game)); + } else { + this.rarity = card.getRarity(); + this.isToken = false; + } + + this.extraDeckCard = card.isExtraDeckCard(); + + // transformable, double faces cards + this.transformable = card.isTransformable(); + + Card secondSideCard = card.getSecondCardFace(); + if (secondSideCard != null) { + this.secondCardFace = new CardView(secondSideCard, game); + this.alternateName = secondCardFace.getName(); + } + + this.flipCard = card.isFlipCard(); + if (card.isFlipCard() && card.getFlipCardName() != null) { + this.alternateName = card.getFlipCardName(); + } + + if (card instanceof ModalDoubleFacedCard) { + this.transformable = true; // enable GUI day/night button + ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card; + this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); + this.alternateName = mdfCard.getRightHalfCard().getName(); + } + + Card meldsToCard = card.getMeldsToCard(); + if (meldsToCard != null) { + this.transformable = true; // enable GUI day/night button + this.secondCardFace = new CardView(meldsToCard, game); + this.alternateName = meldsToCard.getName(); + } + + if (card instanceof PermanentToken && card.isTransformable()) { + Token backFace = (Token) ((PermanentToken) card).getOtherFace(); + this.secondCardFace = new CardView(backFace, game); + this.alternateName = backFace.getName(); + } } + // shared info - targets if (card instanceof Spell) { this.mageObjectType = MageObjectType.SPELL; Spell spell = (Spell) card; @@ -524,31 +575,6 @@ public class CardView extends SimpleCardView { } } - // Determine what part of the art to slice out for spells on the stack which originate - // from a split, fuse, or aftermath split card. - // Modal double faces cards draws as normal cards - SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType(); - if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) { - // Needs a special art rect - if (ty == SpellAbilityType.SPLIT_FUSED) { - artRect = ArtRect.SPLIT_FUSED; - } else if (spell.getCard() != null) { - SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard(); - Abilities aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game); - if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) { - if (ty == SpellAbilityType.SPLIT_RIGHT) { - artRect = ArtRect.AFTERMATH_BOTTOM; - } else { - artRect = ArtRect.AFTERMATH_TOP; - } - } else if (ty == SpellAbilityType.SPLIT_RIGHT) { - artRect = ArtRect.SPLIT_RIGHT; - } else { - artRect = ArtRect.SPLIT_LEFT; - } - } - } - // show for modal spell, which mode was chosen if (spell.getSpellAbility().isModal()) { for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { @@ -571,24 +597,62 @@ public class CardView extends SimpleCardView { } } } - } } - // Frame color - this.frameColor = card.getFrameColor(game).copy(); + // render info + if (showFaceUp) { + if (card instanceof Spell) { + Spell spell = (Spell) card; + // Determine what part of the art to slice out for spells on the stack which originate + // from a split, fuse, or aftermath split card. + // Modal double faces cards draws as normal cards + SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType(); + if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) { + // Needs a special art rect + if (ty == SpellAbilityType.SPLIT_FUSED) { + artRect = ArtRect.SPLIT_FUSED; + } else if (spell.getCard() != null) { + SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard(); + Abilities aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game); + if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) { + if (ty == SpellAbilityType.SPLIT_RIGHT) { + artRect = ArtRect.AFTERMATH_BOTTOM; + } else { + artRect = ArtRect.AFTERMATH_TOP; + } + } else if (ty == SpellAbilityType.SPLIT_RIGHT) { + artRect = ArtRect.SPLIT_RIGHT; + } else { + artRect = ArtRect.SPLIT_LEFT; + } + } + } + } - // Frame style - this.frameStyle = card.getFrameStyle(); + // Cases, classes and sagas have portrait art + if (card.getSubtype().contains(SubType.CASE) || + card.getSubtype().contains(SubType.CLASS)) { + artRect = ArtRect.FULL_LENGTH_LEFT; + } else if (card.getSubtype().contains(SubType.SAGA)) { + artRect = ArtRect.FULL_LENGTH_RIGHT; + } - // Get starting loyalty - this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); + // Frame color + this.frameColor = card.getFrameColor(game).copy(); - // Get starting defense - this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); + // Frame style + this.frameStyle = card.getFrameStyle(); - // add card icons at the end, so it will have full card view data - this.generateCardIcons(null, card, game); + // Get starting loyalty + this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); + + // Get starting defense + this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); + + // add card icons at the end, so it will have full card view data + this.generateCardIcons(null, card, game); + } } /** @@ -723,11 +787,14 @@ public class CardView extends SimpleCardView { } } + @Deprecated // TODO: research and raplace all usages to normal calls, see constructors for EmblemView and other public CardView(MageObject object, Game game) { super(object.getId(), object.getExpansionSetCode(), object.getCardNumber(), false, true); this.setOriginalValues(object); + this.imageFileName = object.getImageFileName(); this.imageNumber = object.getImageNumber(); + this.name = object.getName(); this.displayName = object.getName(); this.displayFullName = object.getName(); @@ -753,7 +820,7 @@ public class CardView extends SimpleCardView { if (object instanceof PermanentToken) { this.mageObjectType = MageObjectType.TOKEN; PermanentToken permanentToken = (PermanentToken) object; - this.rarity = Rarity.COMMON; + this.rarity = Rarity.SPECIAL; this.rules = new ArrayList<>(permanentToken.getRules(game)); } else if (object instanceof Emblem) { this.mageObjectType = MageObjectType.EMBLEM; @@ -787,6 +854,12 @@ public class CardView extends SimpleCardView { this.rules = new ArrayList<>(); this.rules.add(stackAbility.getRule()); } + if (object.getSubtype().contains(SubType.CASE) || + object.getSubtype().contains(SubType.CLASS)) { + artRect = ArtRect.FULL_LENGTH_LEFT; + } else if (object.getSubtype().contains(SubType.SAGA)) { + artRect = ArtRect.FULL_LENGTH_RIGHT; + } // Frame color this.frameColor = object.getFrameColor(game).copy(); // Frame style @@ -814,9 +887,10 @@ public class CardView extends SimpleCardView { this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = emblem.getExpansionSetCode(); this.cardNumber = emblem.getCardNumber(); + this.imageFileName = emblem.getImageFileName(); this.imageNumber = emblem.getImageNumber(); this.usesVariousArt = emblem.getUsesVariousArt(); - this.rarity = Rarity.COMMON; + this.rarity = Rarity.SPECIAL; this.playableStats = emblem.playableStats.copy(); this.isChoosable = emblem.isChoosable(); @@ -836,8 +910,9 @@ public class CardView extends SimpleCardView { this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = dungeon.getExpansionSetCode(); this.cardNumber = ""; + this.imageFileName = ""; this.imageNumber = 0; - this.rarity = Rarity.COMMON; + this.rarity = Rarity.SPECIAL; this.playableStats = dungeon.playableStats.copy(); this.isChoosable = dungeon.isChoosable(); @@ -858,8 +933,9 @@ public class CardView extends SimpleCardView { this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = plane.getExpansionSetCode(); this.cardNumber = ""; + this.imageFileName = ""; this.imageNumber = 0; - this.rarity = Rarity.COMMON; + this.rarity = Rarity.SPECIAL; this.playableStats = plane.playableStats.copy(); this.isChoosable = plane.isChoosable(); @@ -880,8 +956,9 @@ public class CardView extends SimpleCardView { this.cardNumber = designation.getCardNumber(); this.expansionSetCode = designation.getExpansionSetCode(); this.cardNumber = ""; + this.imageFileName = ""; this.imageNumber = 0; - this.rarity = Rarity.COMMON; + this.rarity = Rarity.SPECIAL; // no playable/chooseable marks for designations } @@ -890,7 +967,7 @@ public class CardView extends SimpleCardView { if (!empty) { throw new IllegalArgumentException("Not supported."); } - fillEmpty(null, false); + fillEmptyWithImageInfo(null, null, false); } public static boolean cardViewEquals(CardView a, CardView b) { // TODO: This belongs in CardView @@ -913,14 +990,21 @@ public class CardView extends SimpleCardView { && a.getManaCostStr().equals(b.getManaCostStr()) && a.getRules().equals(b.getRules()) && Objects.equals(a.getRarity(), b.getRarity()) - && Objects.equals(a.getCardNumber(), b.getCardNumber()) - && Objects.equals(a.getExpansionSetCode(), b.getExpansionSetCode()) + && a.getFrameStyle() == b.getFrameStyle() && Objects.equals(a.getCounters(), b.getCounters()) && a.isFaceDown() == b.isFaceDown())) { return false; } + if (!(Objects.equals(a.getExpansionSetCode(), b.getExpansionSetCode()) + && Objects.equals(a.getCardNumber(), b.getCardNumber()) + && Objects.equals(a.getImageNumber(), b.getImageNumber()) + && Objects.equals(a.getImageFileName(), b.getImageFileName()) + )) { + return false; + } + if (!(a instanceof PermanentView)) { return true; } @@ -930,10 +1014,16 @@ public class CardView extends SimpleCardView { && aa.getDamage() == bb.getDamage(); } - private void fillEmpty(Card card, boolean controlled) { - this.name = "Face Down"; - this.displayName = name; - this.displayFullName = name; + private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isFaceDown) { + this.name = ""; + this.displayName = ""; + this.displayFullName = ""; + this.expansionSetCode = ""; + this.cardNumber = "0"; + this.imageFileName = ""; + this.imageNumber = 0; + this.usesVariousArt = false; + this.rules = new ArrayList<>(); this.power = ""; this.toughness = ""; @@ -950,33 +1040,68 @@ public class CardView extends SimpleCardView { this.manaCostLeftStr = new ArrayList<>(); this.manaCostRightStr = new ArrayList<>(); this.manaValue = 0; + this.rarity = Rarity.SPECIAL; // hide rarity info - // the controller can see more information (e.g. enlarged image) than other players for face down cards (e.g. Morph played face down) - if (!controlled) { - this.rarity = Rarity.COMMON; - this.expansionSetCode = ""; - this.cardNumber = "0"; - this.imageNumber = 0; - } else { - this.rarity = card.getRarity(); - } + if (imageSourceCard != null) { + // keep inner images info (server side card already contain actual info) + String imageSetCode = imageSourceCard.getExpansionSetCode(); + String imageCardNumber = imageSourceCard.getCardNumber(); + String imageFileName = imageSourceCard.getImageFileName(); + Integer imageNumber = imageSourceCard.getImageNumber(); + boolean imageUsesVariousArt = imageSourceCard.getUsesVariousArt(); + if (imageSetCode.equals(TokenRepository.XMAGE_TOKENS_SET_CODE)) { + this.expansionSetCode = imageSetCode; + this.cardNumber = imageCardNumber; + this.imageFileName = imageFileName; + this.imageNumber = imageNumber; + this.usesVariousArt = imageUsesVariousArt; + } - if (card != null) { - if (card instanceof Permanent) { + if (imageSourceCard instanceof PermanentToken) { + this.mageObjectType = MageObjectType.TOKEN; + } else if (imageSourceCard instanceof Permanent) { this.mageObjectType = MageObjectType.PERMANENT; - } else if (card.isCopy()) { + } else if (imageSourceCard.isCopy()) { this.mageObjectType = MageObjectType.COPY_CARD; + } else if (imageSourceCard instanceof Spell) { + this.mageObjectType = MageObjectType.SPELL; } else { this.mageObjectType = MageObjectType.CARD; } - if (card instanceof PermanentToken) { - this.mageObjectType = MageObjectType.TOKEN; - } - if (card instanceof Spell) { - this.mageObjectType = MageObjectType.SPELL; - } } + // make default face down image + // TODO: implement diff backface images someday and insert here (user data + card owner) + if (isFaceDown && this.imageFileName.isEmpty()) { + this.name = ""; + this.displayName = this.name; + this.displayFullName = this.name; + + // as foretell face down + // TODO: it's not ok to use that code - server side objects must has all data, see BecomesFaceDownCreatureEffect.makeFaceDownObject + // it must be a more global bug for card characteristics, not client side viewer + if (game != null && imageSourceCard != null && ForetellAbility.isCardInForetell(imageSourceCard, game)) { + TokenInfo tokenInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, this.getId()); + if (tokenInfo != null) { + this.expansionSetCode = tokenInfo.getSetCode(); + this.cardNumber = "0"; + this.imageFileName = tokenInfo.getName(); + this.imageNumber = tokenInfo.getImageNumber(); + this.usesVariousArt = false; + } + return; + } + + // as normal face down + TokenInfo tokenInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL, this.getId()); + if (tokenInfo != null) { + this.expansionSetCode = tokenInfo.getSetCode(); + this.cardNumber = "0"; + this.imageFileName = tokenInfo.getName(); + this.imageNumber = tokenInfo.getImageNumber(); + this.usesVariousArt = false; + } + } } CardView(Token token, Game game) { @@ -1003,9 +1128,9 @@ public class CardView extends SimpleCardView { this.manaCostRightStr = new ArrayList<>(); this.rarity = Rarity.SPECIAL; - // source object is a token, so no card number this.expansionSetCode = token.getExpansionSetCode(); this.cardNumber = token.getCardNumber(); + this.imageFileName = token.getImageFileName(); this.imageNumber = token.getImageNumber(); } @@ -1040,18 +1165,15 @@ public class CardView extends SimpleCardView { if (object == null) { return; } - // Only valid objects to transfer original values are Card and Token + // only valid objects to transfer original values are Card and Token if (object instanceof Card || object instanceof Token) { this.originalPower = object.getPower(); this.originalToughness = object.getToughness(); this.originalIsCopy = object.isCopy(); - this.originalId = object.getId(); - if (object instanceof Card) { - this.originalColorIdentity = ((Card) object).getColorIdentity(); - this.originalIsCard = true; - } else if (object instanceof Token) { - this.originalColorIdentity = ManaUtil.getColorIdentity((Token) object); + this.originalColorIdentity = findColorIdentityStr(((Card) object).getColorIdentity()); + } else { + this.originalColorIdentity = findColorIdentityStr(ManaUtil.getColorIdentity((Token) object)); } } } @@ -1167,9 +1289,8 @@ public class CardView extends SimpleCardView { return rarity; } - public String getColorIdentityStr() { - FilterMana colorInfo = this.originalColorIdentity; - if (colorInfo != null) { + public String findColorIdentityStr(FilterMana colorInfo) { + if (colorInfo == null) { colorInfo = new FilterMana(); } @@ -1327,6 +1448,10 @@ public class CardView extends SimpleCardView { return bandedCards; } + public String getImageFileName() { + return imageFileName; + } + public int getImageNumber() { return imageNumber; } @@ -1465,18 +1590,14 @@ public class CardView extends SimpleCardView { return this.originalToughness; } - public UUID getOriginalId() { - return this.originalId; + public String getOriginalColorIdentity() { + return this.originalColorIdentity != null ? this.originalColorIdentity : ""; } public boolean isOriginalACopy() { return this.originalIsCopy; } - public boolean isOriginalACard() { - return this.originalIsCard; - } - public List getCardIcons() { return this.cardIcons; } diff --git a/Mage.Common/src/main/java/mage/view/CardsView.java b/Mage.Common/src/main/java/mage/view/CardsView.java index cd0b2832a93..43d793d2f8e 100644 --- a/Mage.Common/src/main/java/mage/view/CardsView.java +++ b/Mage.Common/src/main/java/mage/view/CardsView.java @@ -12,6 +12,7 @@ import mage.game.command.Plane; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.target.targetpointer.TargetPointer; +import mage.util.CardUtil; import mage.util.GameLog; import org.apache.log4j.Logger; @@ -50,15 +51,15 @@ public class CardsView extends LinkedHashMap { } } - public CardsView(Game game, Collection cards) { + public CardsView(Game game, Collection cards, UUID createdForPlayerId) { for (Card card : cards) { - this.put(card.getId(), new CardView(card, game, false)); + this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); } } - public CardsView(Game game, Collection cards, boolean showFaceDown, boolean storeZone) { + public CardsView(Game game, Collection cards, UUID createdForPlayerId, boolean storeZone) { for (Card card : cards) { - this.put(card.getId(), new CardView(card, game, false, showFaceDown, storeZone)); + this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId), storeZone)); } } diff --git a/Mage.Common/src/main/java/mage/view/CommandObjectView.java b/Mage.Common/src/main/java/mage/view/CommandObjectView.java index 66519a32304..0bf91afc9b7 100644 --- a/Mage.Common/src/main/java/mage/view/CommandObjectView.java +++ b/Mage.Common/src/main/java/mage/view/CommandObjectView.java @@ -14,6 +14,8 @@ public interface CommandObjectView extends SelectableObjectView { UUID getId(); + String getImageFileName(); + int getImageNumber(); List getRules(); diff --git a/Mage.Common/src/main/java/mage/view/CommanderView.java b/Mage.Common/src/main/java/mage/view/CommanderView.java index b99c6f72cb9..d174a534184 100644 --- a/Mage.Common/src/main/java/mage/view/CommanderView.java +++ b/Mage.Common/src/main/java/mage/view/CommanderView.java @@ -2,10 +2,13 @@ package mage.view; import java.io.Serializable; +import java.util.UUID; + import mage.cards.Card; import mage.constants.MageObjectType; import mage.game.Game; import mage.game.command.Commander; +import mage.util.CardUtil; /** * @@ -13,8 +16,8 @@ import mage.game.command.Commander; */ public class CommanderView extends CardView implements CommandObjectView, Serializable{ - public CommanderView(Commander commander, Card sourceCard, Game game) { - super(sourceCard, game, false); + public CommanderView(Commander commander, Card sourceCard, Game game, UUID createdForPlayerId) { + super(sourceCard, game, CardUtil.canShowAsControlled(sourceCard, createdForPlayerId)); this.mageObjectType = MageObjectType.COMMANDER; } } diff --git a/Mage.Common/src/main/java/mage/view/DungeonView.java b/Mage.Common/src/main/java/mage/view/DungeonView.java index 4818c70dc09..93e8b604de4 100644 --- a/Mage.Common/src/main/java/mage/view/DungeonView.java +++ b/Mage.Common/src/main/java/mage/view/DungeonView.java @@ -14,15 +14,17 @@ public class DungeonView implements CommandObjectView, Serializable { protected UUID id; protected String name; - protected int imageNum; - protected String expansionSetCode; + protected String imageFileName = ""; + protected int imageNumber = 0; + protected String expansionSetCode = ""; protected List rules; protected PlayableObjectStats playableStats = new PlayableObjectStats(); public DungeonView(Dungeon dungeon) { this.id = dungeon.getId(); this.name = dungeon.getName(); - this.imageNum = dungeon.getImageNumber(); + this.imageFileName = dungeon.getImageFileName(); + this.imageNumber = dungeon.getImageNumber(); this.expansionSetCode = dungeon.getExpansionSetCode(); this.rules = dungeon.getRules(); } @@ -42,9 +44,14 @@ public class DungeonView implements CommandObjectView, Serializable { return id; } + @Override + public String getImageFileName() { + return imageFileName; + } + @Override public int getImageNumber() { - return imageNum; + return imageNumber; } @Override diff --git a/Mage.Common/src/main/java/mage/view/EmblemView.java b/Mage.Common/src/main/java/mage/view/EmblemView.java index 23b3768e967..7f360d3823d 100644 --- a/Mage.Common/src/main/java/mage/view/EmblemView.java +++ b/Mage.Common/src/main/java/mage/view/EmblemView.java @@ -16,7 +16,8 @@ public class EmblemView implements CommandObjectView, Serializable { protected UUID id; protected String name; protected String cardNumber = ""; - protected int imageNum; + protected String imageFileName = ""; + protected int imageNumber; protected boolean usesVariousArt = false; protected String expansionSetCode; protected List rules; @@ -25,7 +26,8 @@ public class EmblemView implements CommandObjectView, Serializable { public EmblemView(Emblem emblem) { this.id = emblem.getId(); this.name = emblem.getName(); - this.imageNum = emblem.getImageNumber(); + this.imageFileName = emblem.getImageFileName(); + this.imageNumber = emblem.getImageNumber(); this.expansionSetCode = emblem.getExpansionSetCode(); this.rules = emblem.getAbilities().getRules(emblem.getName()); if (emblem instanceof EmblemOfCard) { @@ -54,9 +56,15 @@ public class EmblemView implements CommandObjectView, Serializable { } @Override - public int getImageNumber() { - return imageNum; + public String getImageFileName() { + return imageFileName; } + + @Override + public int getImageNumber() { + return imageNumber; + } + public boolean getUsesVariousArt() { return this.usesVariousArt; } diff --git a/Mage.Common/src/main/java/mage/view/ExileView.java b/Mage.Common/src/main/java/mage/view/ExileView.java index f460fdfc9cc..bfcafcfb5a9 100644 --- a/Mage.Common/src/main/java/mage/view/ExileView.java +++ b/Mage.Common/src/main/java/mage/view/ExileView.java @@ -6,6 +6,7 @@ import java.util.UUID; import mage.cards.Card; import mage.game.ExileZone; import mage.game.Game; +import mage.util.CardUtil; /** * @@ -17,11 +18,11 @@ public class ExileView extends CardsView { private final String name; private final UUID id; - public ExileView(ExileZone exileZone, Game game) { + public ExileView(ExileZone exileZone, Game game, UUID createdForPlayerId) { this.name = exileZone.getName(); this.id = exileZone.getId(); for (Card card: exileZone.getCards(game)) { - this.put(card.getId(), new CardView(card, game, false)); + this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); } } diff --git a/Mage.Common/src/main/java/mage/view/GameView.java b/Mage.Common/src/main/java/mage/view/GameView.java index f7d5b82dc58..46701309e56 100644 --- a/Mage.Common/src/main/java/mage/view/GameView.java +++ b/Mage.Common/src/main/java/mage/view/GameView.java @@ -24,6 +24,7 @@ import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.players.PlayableObjectsList; import mage.players.Player; +import mage.util.CardUtil; import org.apache.log4j.Logger; import java.io.Serializable; @@ -73,15 +74,16 @@ public class GameView implements Serializable { if (player.getId().equals(createdForPlayerId)) { createdForPlayer = player; this.myPlayerId = player.getId(); - this.myHand.putAll(new CardsView(game, player.getHand().getCards(game))); + this.myHand.putAll(new CardsView(game, player.getHand().getCards(game), createdForPlayerId)); } } for (StackObject stackObject : state.getStack()) { if (stackObject instanceof Spell) { // Spell - CardView spellView = new CardView((Spell) stackObject, game, stackObject.getControllerId().equals(createdForPlayerId)); - spellView.paid = ((Spell) stackObject).getSpellAbility().getManaCostsToPay().isPaid(); - stack.put(stackObject.getId(), spellView); + Spell spell = (Spell) stackObject; + CardView spellView = new CardView(spell, game, CardUtil.canShowAsControlled(spell, createdForPlayerId)); + spellView.paid = spell.getSpellAbility().getManaCostsToPay().isPaid(); + stack.put(spell.getId(), spellView); } else if (stackObject instanceof StackAbility) { // Stack Ability MageObject object = game.getObject(stackObject.getSourceId()); @@ -93,9 +95,9 @@ public class GameView implements Serializable { if (object != null) { if (object instanceof Permanent) { boolean controlled = ((Permanent) object).getControllerId().equals(createdForPlayerId); - stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, object.getName(), object, new CardView(((Permanent) object), game, controlled, false, false))); + stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, object.getName(), object, new CardView(((Permanent) object), game, controlled, false))); } else { - stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, card.getName(), card, new CardView(card, game, false, false, false))); + stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, card.getName(), card, new CardView(card, game, false, false))); } } else { stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, "", card, new CardView(card, game))); @@ -132,9 +134,9 @@ public class GameView implements Serializable { } else if (object instanceof Designation) { Designation designation = (Designation) game.getObject(object.getId()); if (designation != null) { - stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, designation.getName(), designation, new CardView(designation, game))); + stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, designation.getName(), designation, new CardView(designation, (StackAbility) stackObject))); } else { - LOGGER.fatal("Designation object not found: " + object.getName() + ' ' + object.toString() + ' ' + object.getClass().toString()); + throw new IllegalArgumentException("Designation object not found: " + object + " - " + object.getClass().toString()); } } else if (object instanceof StackAbility) { StackAbility stackAbility = ((StackAbility) object); @@ -142,20 +144,19 @@ public class GameView implements Serializable { stack.put(stackObject.getId(), new CardView(stackObject, game)); checkPaid(stackObject.getId(), ((StackAbility) stackObject)); } else { - LOGGER.fatal("Object can't be cast to StackAbility: " + object.getName() + ' ' + object.toString() + ' ' + object.getClass().toString()); + throw new IllegalArgumentException("Object can't be cast to StackAbility: " + object + " - " + object.getClass().toString()); } } else { // can happen if a player times out while ability is on the stack LOGGER.debug("Stack Object for stack ability not found: " + stackObject.getStackAbility().getRule()); } } else if (stackObject != null) { - LOGGER.fatal("Unknown type of StackObject: " + stackObject.getName() + ' ' + stackObject.toString() + ' ' + stackObject.getClass().toString()); + throw new IllegalArgumentException("Unknown type of StackObject: " + stackObject + " - " + stackObject.getClass().toString()); } - //stackOrder.add(stackObject.getId()); } - //Collections.reverse(stackOrder); + for (ExileZone exileZone : state.getExile().getExileZones()) { - exiles.add(new ExileView(exileZone, game)); + exiles.add(new ExileView(exileZone, game, createdForPlayerId)); } for (String name : state.getRevealed().keySet()) { revealed.add(new RevealedView(name, state.getRevealed().get(name), game)); diff --git a/Mage.Common/src/main/java/mage/view/PermanentView.java b/Mage.Common/src/main/java/mage/view/PermanentView.java index a26893a1994..ee61d369c69 100644 --- a/Mage.Common/src/main/java/mage/view/PermanentView.java +++ b/Mage.Common/src/main/java/mage/view/PermanentView.java @@ -1,17 +1,15 @@ package mage.view; -import mage.abilities.Ability; -import mage.abilities.common.TurnFaceUpAbility; import mage.cards.Card; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.players.Player; +import mage.util.CardUtil; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com @@ -33,45 +31,46 @@ public class PermanentView extends CardView { private final boolean controlled; private final UUID attachedTo; private final boolean morphed; + private final boolean disguised; private final boolean manifested; private final boolean attachedToPermanent; // If this card is attached to a permanent which is controlled by a player other than the one which controls this permanent private final boolean attachedControllerDiffers; public PermanentView(Permanent permanent, Card card, UUID createdForPlayerId, Game game) { - super(permanent, game, permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId)); + super(permanent, game, CardUtil.canShowAsControlled(permanent, createdForPlayerId)); this.controlled = permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId); - this.rules = permanent.getRules(game); this.tapped = permanent.isTapped(); this.flipped = permanent.isFlipped(); this.phasedIn = permanent.isPhasedIn(); this.summoningSickness = permanent.hasSummoningSickness(); this.morphed = permanent.isMorphed(); + this.disguised = permanent.isDisguised(); this.manifested = permanent.isManifested(); this.damage = permanent.getDamage(); this.attachments = new ArrayList<>(permanent.getAttachments()); this.attachedTo = permanent.getAttachedTo(); - // show face down cards to all players at the game end - boolean showFaceDownInfo = controlled || (game != null && game.hasEnded()); - + // store original card, e.g. for sides switch in GUI if (isToken()) { original = new CardView(((PermanentToken) permanent).getToken().copy(), (Game) null); - original.expansionSetCode = permanent.getExpansionSetCode(); + original.expansionSetCode = permanent.getExpansionSetCode(); // TODO: miss card number and other? expansionSetCode = permanent.getExpansionSetCode(); } else { + // face down card must be hidden from opponent, but shown on game end for all + boolean showFaceDownInfo = controlled || (game != null && game.hasEnded()); if (card != null && showFaceDownInfo) { - // face down card must be hidden from opponent, but shown on game end for all original = new CardView(card.copy(), (Game) null); } else { original = null; } } - this.transformed = permanent.isTransformed(); + //this.transformed = permanent.isTransformed(); this.copy = permanent.isCopy(); // for fipped, transformed or copied cards, switch the names if (original != null && !original.getName().equals(this.getName())) { + // TODO: wtf, why copy check here?! Need research if (permanent.isCopy() && permanent.isFlipCard()) { this.alternateName = permanent.getFlipCardName(); } else { @@ -98,33 +97,6 @@ public class PermanentView extends CardView { } this.nameController = nameController; - // add info for face down permanents - if (permanent.isFaceDown(game) && card != null) { - if (showFaceDownInfo) { - // must be a morphed or manifested card - for (Ability permanentAbility : permanent.getAbilities(game)) { - if (permanentAbility.getWorksFaceDown()) { - this.rules.add(permanentAbility.getRule(true)); - } else if (permanentAbility instanceof TurnFaceUpAbility && !permanentAbility.getRuleVisible()) { - this.rules.add(permanentAbility.getRule()); - } - } - this.name = card.getName(); - this.displayName = card.getName(); - this.expansionSetCode = card.getExpansionSetCode(); - this.cardNumber = card.getCardNumber(); - } else { - if (permanent.isManifested()) { - this.rules.add("A manifested creature card can be turned face up any time for it's mana cost." - + " A face-down card can also be turned face up for its morph cost."); - } else if (permanent.isMorphed()) { - this.rules.add("If the controller has priority, they may turn this permanent face up." - + " This is a special action; it doesn't use the stack. To do this they pay the morph costs," - + " then turns this permanent face up."); - } - } - } - // determines if shown in it's own column boolean attachedToPermanent = false; boolean attachedControllerDiffers = false; @@ -169,6 +141,7 @@ public class PermanentView extends CardView { this.nameController = permanentView.nameController; this.attachedTo = permanentView.attachedTo; this.morphed = permanentView.morphed; + this.disguised = permanentView.disguised; this.manifested = permanentView.manifested; this.attachedToPermanent = permanentView.attachedToPermanent; this.attachedControllerDiffers = permanentView.attachedControllerDiffers; @@ -242,6 +215,10 @@ public class PermanentView extends CardView { return morphed; } + public boolean isDisguised() { + return disguised; + } + public boolean isManifested() { return manifested; } diff --git a/Mage.Common/src/main/java/mage/view/PlaneView.java b/Mage.Common/src/main/java/mage/view/PlaneView.java index 0372f5b364d..d8511132994 100644 --- a/Mage.Common/src/main/java/mage/view/PlaneView.java +++ b/Mage.Common/src/main/java/mage/view/PlaneView.java @@ -1,6 +1,5 @@ package mage.view; -import mage.cards.Card; import mage.game.command.Plane; import mage.players.PlayableObjectStats; @@ -15,15 +14,17 @@ public class PlaneView implements CommandObjectView, Serializable { protected UUID id; protected String name; - protected int imageNum; - protected String expansionSetCode; + protected String imageFileName = ""; + protected int imageNumber = 0; + protected String expansionSetCode = ""; protected List rules; protected PlayableObjectStats playableStats = new PlayableObjectStats(); public PlaneView(Plane plane) { this.id = plane.getId(); this.name = plane.getName(); - this.imageNum = plane.getImageNumber(); + this.imageFileName = plane.getImageFileName(); + this.imageNumber = plane.getImageNumber(); this.expansionSetCode = plane.getExpansionSetCode(); this.rules = plane.getAbilities().getRules(plane.getName()); } @@ -43,9 +44,14 @@ public class PlaneView implements CommandObjectView, Serializable { return id; } + @Override + public String getImageFileName() { + return imageFileName; + } + @Override public int getImageNumber() { - return imageNum; + return imageNumber; } @Override diff --git a/Mage.Common/src/main/java/mage/view/PlayerView.java b/Mage.Common/src/main/java/mage/view/PlayerView.java index 0e2b136afec..326b243319b 100644 --- a/Mage.Common/src/main/java/mage/view/PlayerView.java +++ b/Mage.Common/src/main/java/mage/view/PlayerView.java @@ -10,6 +10,7 @@ import mage.game.command.*; import mage.game.permanent.Permanent; import mage.players.Player; import mage.players.net.UserData; +import mage.util.CardUtil; import java.io.Serializable; import java.util.*; @@ -82,19 +83,19 @@ public class PlayerView implements Serializable { this.hasLeft = player.hasLeft(); for (Card card : player.getGraveyard().getCards(game)) { - graveyard.put(card.getId(), new CardView(card, game, false)); + graveyard.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); } for (ExileZone exileZone : game.getExile().getExileZones()) { for (Card card : exileZone.getCards(game)) { if (player.getId().equals(card.getOwnerId())) { - exile.put(card.getId(), new CardView(card, game, false)); // unnown if it's allowed to look under a face down card + exile.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); } } } if (this.controlled || !player.isHuman()) { // sideboard available for itself or for computer only for (Card card : player.getSideboard().getCards(game)) { - sideboard.put(card.getId(), new CardView(card, game, false)); + sideboard.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); } } @@ -137,7 +138,7 @@ public class PlayerView implements Serializable { if (commander.getControllerId().equals(this.playerId)) { Card sourceCard = game.getCard(commander.getSourceId()); if (sourceCard != null) { - commandList.add(new CommanderView(commander, sourceCard, game)); + commandList.add(new CommanderView(commander, sourceCard, game, createdForPlayerId)); } } } diff --git a/Mage.Common/src/main/java/mage/view/RevealedView.java b/Mage.Common/src/main/java/mage/view/RevealedView.java index 51fc22f27a7..46c34b216d3 100644 --- a/Mage.Common/src/main/java/mage/view/RevealedView.java +++ b/Mage.Common/src/main/java/mage/view/RevealedView.java @@ -19,7 +19,7 @@ public class RevealedView implements Serializable { public RevealedView(String name, Cards cards, Game game) { this.name = name; for (Card card : cards.getCards(game)) { - this.cards.put(card.getId(), new CardView(card, game, card.getId())); + this.cards.put(card.getId(), new CardView(card, game)); } } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Premodern.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Premodern.java index 9db3ab31135..3026bc63ebe 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Premodern.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Premodern.java @@ -42,8 +42,14 @@ public class Premodern extends Constructed { setCodes.add(mage.sets.Legions.getInstance().getCode()); setCodes.add(mage.sets.Scourge.getInstance().getCode()); - // Ban List see: https://scryfall.com/search?q=banned%3Apremodern&as=checklist - // Updated last time 02-08-2023 (https://premodernmagic.com/blog/ban-list-update-2023/) + + banned.clear(); // must be independent of actual constructed formats + + // official premodern list: https://premodernmagic.com/banned-watched#ban-list + // official api list: https://premodernmagic.com/_serverside/get-banned-cards.php + // scryfall search: https://scryfall.com/search?q=banned%3Apremodern&as=checklist + // last updated: 2024-03-07 + banned.add("Amulet of Quoz"); banned.add("Balance"); banned.add("Brainstorm"); @@ -60,8 +66,8 @@ public class Premodern extends Constructed { banned.add("Land Tax"); banned.add("Mana Vault"); banned.add("Memory Jar"); - banned.add("Mind's Desire"); banned.add("Mind Twist"); + banned.add("Mind's Desire"); banned.add("Mystical Tutor"); banned.add("Necropotence"); banned.add("Rebirth"); diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java index 42b5cdbec50..d4b8b571f08 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java @@ -49,7 +49,7 @@ public class MomirDuel extends GameImpl { Player player = getPlayer(playerId); if (player != null) { CardInfo cardInfo = CardRepository.instance.findCard("Momir Vig, Simic Visionary"); - addEmblem(new MomirEmblem(), cardInfo.getCard(), playerId); + addEmblem(new MomirEmblem(), cardInfo.createCard(), playerId); } } getState().addAbility(ability, null); diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java index 470696ae11a..ccb28b895a9 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java @@ -55,7 +55,7 @@ public class MomirGame extends GameImpl { // how-to fix: make sure that a Momir Emblem and a source card uses same set (DIS - Dissension) throw new IllegalStateException("Wrong code usage: momir card and emblem must exists in the same set (DIS)"); } - addEmblem(new MomirEmblem(), cardInfo.getCard(), playerId); + addEmblem(new MomirEmblem(), cardInfo.createCard(), playerId); } } getState().addAbility(ability, null); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java index ed27d2a4479..78b47598bd5 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java @@ -85,6 +85,7 @@ public final class ArtificialScoringSystem { MageObject object = game.getObject(uuid); if (object instanceof Card) { Card card = (Card) object; + // TODO: implement getOutcomeTotal for permanents and cards too (not only attachments) int outcomeScore = card.getAbilities(game).getOutcomeTotal(); if (card.getCardType(game).contains(CardType.ENCHANTMENT)) { enchantments = enchantments + outcomeScore * 100; diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 741c0d4e7ff..e2da7dd0a70 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -1301,7 +1301,6 @@ public class ComputerPlayer extends PlayerImpl { @Override public boolean priority(Game game) { game.resumeTimer(getTurnControlledBy()); - log.debug("priority"); boolean result = priorityPlay(game); game.pauseTimer(getTurnControlledBy()); return result; @@ -2284,7 +2283,7 @@ public class ComputerPlayer extends PlayerImpl { } for (int i = 0; i < number; i++) { - Card land = cards.get(RandomUtil.nextInt(cards.size())).getCard(); + Card land = cards.get(RandomUtil.nextInt(cards.size())).createCard(); deck.getCards().add(land); } } diff --git a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java index 2315544dd7f..de1c4e24081 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java @@ -153,7 +153,7 @@ public class ChatManagerImpl implements ChatManager { CardInfo cardInfo = CardRepository.instance.findCard(searchName, true); if (cardInfo != null) { String newMessagePart = GameLog.getColoredObjectIdName( - cardInfo.getCard().getColor(), + cardInfo.createCard().getColor(), UUID.randomUUID(), cardInfo.getName(), "", diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 1b9d83063a1..51b4c2e7011 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -24,7 +24,6 @@ import mage.players.Player; import mage.server.Main; import mage.server.User; import mage.server.managers.ManagerFactory; -import mage.server.util.Splitter; import mage.util.MultiAmountMessage; import mage.utils.StreamUtils; import mage.utils.timer.PriorityTimer; @@ -174,8 +173,8 @@ public class GameController implements GameCallback { timer.pause(); break; } - } catch (MageException ex) { - logger.fatal("Table event listener error ", ex); + } catch (MageException e) { + logger.fatal("Table event listener error: " + e, e); } } ); @@ -823,7 +822,7 @@ public class GameController implements GameCallback { } private synchronized void choosePile(UUID playerId, final String message, final List pile1, final List pile2) throws MageException { - perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(game, pile1), new CardsView(game, pile2))); + perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(game, pile1, playerId), new CardsView(game, pile2, playerId))); } private synchronized void chooseMode(UUID playerId, final Map modes, final String message) throws MageException { @@ -837,12 +836,7 @@ public class GameController implements GameCallback { private synchronized void target(UUID playerId, final String question, final Cards cards, final List perms, final Set targets, final boolean required, final Map options) throws MageException { perform(playerId, playerId1 -> { if (cards != null) { - // Zone targetZone = (Zone) options.get("targetZone"); - // Are there really situations where a player selects from a list of face down cards? - // So always show face up for selection - // boolean showFaceDown = targetZone != null && targetZone.equals(Zone.PICK); - boolean showFaceDown = true; - getGameSession(playerId1).target(question, new CardsView(game, cards.getCards(game), showFaceDown, true), targets, required, options); + getGameSession(playerId1).target(question, new CardsView(game, cards.getCards(game), playerId, true), targets, required, options); } else if (perms != null) { CardsView permsView = new CardsView(); for (Permanent perm : perms) { @@ -992,29 +986,24 @@ public class GameController implements GameCallback { } private void perform(UUID playerId, Command command, boolean informOthers) { - if (game.getPlayer(playerId).isGameUnderControl()) { // is the player controlling it's own turn - if (gameSessions.containsKey(playerId)) { - setupTimeout(playerId); - command.execute(playerId); - } - // TODO: if watcher disconnects then game freezes with active timer, must be fix for such use case - // same for another player (can be fixed by super-duper connection) - if (informOthers) { - informOthers(playerId); - } - } else { - List players = Splitter.split(game, playerId); - for (UUID uuid : players) { - if (gameSessions.containsKey(uuid)) { - setupTimeout(uuid); - command.execute(uuid); - } - } - // TODO: if watcher disconnects then game freeze with active timer, must be fix for such use case - // same for another player (can be fixed by super-duper connection) - if (informOthers) { - informOthers(players); - } + Player player = game.getPlayer(playerId); + if (player == null) { + throw new IllegalArgumentException("Can't perform command for unknown player id: " + playerId); + } + + Player realPlayerController = game.getPlayer(player.getTurnControlledBy()); + if (realPlayerController == null) { + throw new IllegalArgumentException("Can't find real turn controller for player id: " + playerId); + } + + if (gameSessions.containsKey(realPlayerController.getId())) { + setupTimeout(realPlayerController.getId()); + command.execute(realPlayerController.getId()); + } + // TODO: if watcher disconnects then game freezes with active timer, must be fix for such use case + // same for another player (can be fixed by super-duper connection) + if (informOthers) { + informOthers(playerId); } } diff --git a/Mage.Server/src/main/java/mage/server/util/Splitter.java b/Mage.Server/src/main/java/mage/server/util/Splitter.java deleted file mode 100644 index 6290a80d47a..00000000000 --- a/Mage.Server/src/main/java/mage/server/util/Splitter.java +++ /dev/null @@ -1,25 +0,0 @@ -package mage.server.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import mage.game.Game; -import mage.players.Player; - -/** - * @author nantuko - */ -public final class Splitter { - - private Splitter(){} - - public static List split(Game game, UUID playerId) { - List players = new ArrayList<>(); - //players.add(playerId); // add original player - Player player = game.getPlayer(playerId); - if (player != null && player.getTurnControlledBy() != null) { - players.add(player.getTurnControlledBy()); - } - return players; - } -} diff --git a/Mage.Sets/src/mage/cards/a/AKillerAmongUs.java b/Mage.Sets/src/mage/cards/a/AKillerAmongUs.java index 9b468396bfd..37d3c617423 100644 --- a/Mage.Sets/src/mage/cards/a/AKillerAmongUs.java +++ b/Mage.Sets/src/mage/cards/a/AKillerAmongUs.java @@ -208,7 +208,7 @@ class AKillerAmongUsEffect extends OneShotEffect { class AKillerAmongUsCost extends CostImpl { AKillerAmongUsCost() { - this.text = "Reveal the chosen creature type"; + this.text = "Reveal the creature type you chose"; } private AKillerAmongUsCost(final AKillerAmongUsCost cost) { diff --git a/Mage.Sets/src/mage/cards/a/AWing.java b/Mage.Sets/src/mage/cards/a/AWing.java index 3dc8f2a0447..d40532abf61 100644 --- a/Mage.Sets/src/mage/cards/a/AWing.java +++ b/Mage.Sets/src/mage/cards/a/AWing.java @@ -76,6 +76,7 @@ class AWingAttacksNextCombatIfAbleSourceEffect extends RequirementEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); turnNumber = game.getTurnNum(); phaseCount = game.getPhase().getCount(); } diff --git a/Mage.Sets/src/mage/cards/a/AbaddonTheDespoiler.java b/Mage.Sets/src/mage/cards/a/AbaddonTheDespoiler.java index 131e01aec8c..82af8794e58 100644 --- a/Mage.Sets/src/mage/cards/a/AbaddonTheDespoiler.java +++ b/Mage.Sets/src/mage/cards/a/AbaddonTheDespoiler.java @@ -18,9 +18,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicate; -import mage.filter.predicate.Predicates; import mage.filter.predicate.card.CastFromZonePredicate; import mage.game.Game; import mage.watchers.common.PlayerLostLifeWatcher; @@ -32,11 +31,10 @@ import java.util.UUID; */ public final class AbaddonTheDespoiler extends CardImpl { - private static final FilterCard filter = new FilterCard(); + private static final FilterNonlandCard filter = new FilterNonlandCard(); static { filter.add(new CastFromZonePredicate(Zone.HAND)); - filter.add(Predicates.not(CardType.LAND.getPredicate())); filter.add(AbaddonTheDespoilerPredicate.instance); } diff --git a/Mage.Sets/src/mage/cards/a/AbuelosAwakening.java b/Mage.Sets/src/mage/cards/a/AbuelosAwakening.java index c59c708a0cb..54a23770f8b 100644 --- a/Mage.Sets/src/mage/cards/a/AbuelosAwakening.java +++ b/Mage.Sets/src/mage/cards/a/AbuelosAwakening.java @@ -58,7 +58,7 @@ public final class AbuelosAwakening extends CardImpl { class AbuelosAwakeningEffect extends ReturnFromGraveyardToBattlefieldTargetEffect { AbuelosAwakeningEffect() { - super(false, false, false); + super(); staticText = "return target artifact or non-Aura enchantment card from your graveyard to the battlefield " + "with X additional +1/+1 counters on it. " + "It's a 1/1 Spirit creature with flying in addition to its other types"; diff --git a/Mage.Sets/src/mage/cards/a/AbyssalGatekeeper.java b/Mage.Sets/src/mage/cards/a/AbyssalGatekeeper.java index c9384ed084a..3e2dab45c5a 100644 --- a/Mage.Sets/src/mage/cards/a/AbyssalGatekeeper.java +++ b/Mage.Sets/src/mage/cards/a/AbyssalGatekeeper.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -9,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -24,7 +23,7 @@ public final class AbyssalGatekeeper extends CardImpl { this.toughness = new MageInt(1); // When Abyssal Gatekeeper dies, each player sacrifices a creature. - this.addAbility(new DiesSourceTriggeredAbility(new SacrificeAllEffect(1, new FilterControlledCreaturePermanent("creature")))); + this.addAbility(new DiesSourceTriggeredAbility(new SacrificeAllEffect(1, StaticFilters.FILTER_PERMANENT_CREATURE))); } private AbyssalGatekeeper(final AbyssalGatekeeper card) { diff --git a/Mage.Sets/src/mage/cards/a/AbyssalGorestalker.java b/Mage.Sets/src/mage/cards/a/AbyssalGorestalker.java index ac29465f60c..c69e284d0d9 100644 --- a/Mage.Sets/src/mage/cards/a/AbyssalGorestalker.java +++ b/Mage.Sets/src/mage/cards/a/AbyssalGorestalker.java @@ -7,8 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,8 +16,6 @@ import java.util.UUID; */ public final class AbyssalGorestalker extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("creatures"); - public AbyssalGorestalker(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); @@ -27,7 +24,7 @@ public final class AbyssalGorestalker extends CardImpl { this.toughness = new MageInt(6); // When Abyssal Gorestalker enters the battlefield, each player sacrifices two creatures. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeAllEffect(2, filter))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeAllEffect(2, StaticFilters.FILTER_PERMANENT_CREATURES))); } private AbyssalGorestalker(final AbyssalGorestalker card) { diff --git a/Mage.Sets/src/mage/cards/a/AcademicDispute.java b/Mage.Sets/src/mage/cards/a/AcademicDispute.java index 580f41daeb8..bcb24f8f16d 100644 --- a/Mage.Sets/src/mage/cards/a/AcademicDispute.java +++ b/Mage.Sets/src/mage/cards/a/AcademicDispute.java @@ -68,7 +68,7 @@ class AcademicDisputeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Permanent permanent = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (permanent != null) { if (player.chooseUse(outcome, "Have " + permanent.getLogName() + " gain reach until end of turn?", source, game)) { GainAbilityTargetEffect effect = new GainAbilityTargetEffect(ReachAbility.getInstance(), Duration.EndOfTurn); diff --git a/Mage.Sets/src/mage/cards/a/AcademicProbation.java b/Mage.Sets/src/mage/cards/a/AcademicProbation.java index 0f5a9c505e3..03163d0bd78 100644 --- a/Mage.Sets/src/mage/cards/a/AcademicProbation.java +++ b/Mage.Sets/src/mage/cards/a/AcademicProbation.java @@ -75,7 +75,7 @@ class AcademicProbationRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/a/AccessDenied.java b/Mage.Sets/src/mage/cards/a/AccessDenied.java index 0776f2cd386..f8e6262dfe0 100644 --- a/Mage.Sets/src/mage/cards/a/AccessDenied.java +++ b/Mage.Sets/src/mage/cards/a/AccessDenied.java @@ -55,7 +55,7 @@ class AccessDeniedEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackObject != null) { game.getStack().counter(source.getFirstTarget(), source, game); return new ThopterColorlessToken().putOntoBattlefield(stackObject.getManaValue(), game, source); diff --git a/Mage.Sets/src/mage/cards/a/AccursedWitch.java b/Mage.Sets/src/mage/cards/a/AccursedWitch.java index a6ad8937615..beb098a87d5 100644 --- a/Mage.Sets/src/mage/cards/a/AccursedWitch.java +++ b/Mage.Sets/src/mage/cards/a/AccursedWitch.java @@ -73,7 +73,7 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player attachTo = game.getPlayer(targetPointer.getFirst(game, source)); + Player attachTo = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || !(game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) || attachTo == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/AcidicDagger.java b/Mage.Sets/src/mage/cards/a/AcidicDagger.java new file mode 100644 index 00000000000..a33f2c051fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AcidicDagger.java @@ -0,0 +1,152 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class AcidicDagger extends CardImpl { + + public AcidicDagger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + // {4}, {tap}: Whenever target creature deals combat damage to a non-Wall creature this turn, + // destroy that non-Wall creature. When the targeted creature leaves the battlefield this turn, + // sacrifice Acidic Dagger. Activate this ability only before blockers are declared. + Ability ability = new ConditionalActivatedAbility( + new CreateDelayedTriggeredAbilityEffect(new AcidicDaggerDestroyNonWallAbility()), + new GenericManaCost(4), + BeforeBlockersAreDeclaredCondition.instance); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AcidicDaggerSacrificeSourceAbility())); + + this.addAbility(ability); + } + + private AcidicDagger(final AcidicDagger card) { + super(card); + } + + @Override + public AcidicDagger copy() { + return new AcidicDagger(this); + } +} + +class AcidicDaggerDestroyNonWallAbility extends DelayedTriggeredAbility { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Wall creature"); + + static { + filter.add(Predicates.not(SubType.WALL.getPredicate())); + } + + AcidicDaggerDestroyNonWallAbility() { + super(new DestroyTargetEffect(), Duration.EndOfTurn, false); + } + + protected AcidicDaggerDestroyNonWallAbility(AcidicDaggerDestroyNonWallAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent damagedPermanent = game.getPermanent(event.getTargetId()); + Permanent attackingPermanent = game.getPermanent(event.getSourceId()); + Permanent targetedPermanent = game.getPermanent(this.getTargets().getFirstTarget()); + + if (damagedPermanent == null + || attackingPermanent == null + || targetedPermanent == null + || !filter.match(damagedPermanent, game) + || !((DamagedEvent) event).isCombatDamage() + || !attackingPermanent.getId().equals(targetedPermanent.getId())) { + return false; + } + + this.getTargets().clear(); // else ability fizzles if target creature died + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(damagedPermanent, game)); + } + return true; + } + + @Override + public AcidicDaggerDestroyNonWallAbility copy() { + return new AcidicDaggerDestroyNonWallAbility(this); + } + + @Override + public String getRule() { + return "Whenever target creature deals combat damage to a non-Wall creature this turn, destroy that non-Wall creature."; + } +} + +// Based on HeartWolfDelayedTriggeredAbility +class AcidicDaggerSacrificeSourceAbility extends DelayedTriggeredAbility { + + AcidicDaggerSacrificeSourceAbility() { + super(new SacrificeSourceEffect(), Duration.EndOfTurn, false); + } + + protected AcidicDaggerSacrificeSourceAbility(AcidicDaggerSacrificeSourceAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getTarget() != null && zEvent.getTargetId().equals(getTargets().getFirstTarget())) { + this.getTargets().clear(); // else ability fizzles because target creature died + return true; + } + return false; + } + + @Override + public AcidicDaggerSacrificeSourceAbility copy() { + return new AcidicDaggerSacrificeSourceAbility(this); + } + + @Override + public String getRule() { + return "When the targeted creature leaves the battlefield this turn, sacrifice {this}."; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ActOfAuthority.java b/Mage.Sets/src/mage/cards/a/ActOfAuthority.java index 3a462e6d2a9..3f7c434efb1 100644 --- a/Mage.Sets/src/mage/cards/a/ActOfAuthority.java +++ b/Mage.Sets/src/mage/cards/a/ActOfAuthority.java @@ -109,10 +109,9 @@ class ActOfAuthorityGainControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent; - if (targetPointer == null) { + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { permanent = game.getPermanent(source.getFirstTarget()); - } else { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); } if (permanent == null) { diff --git a/Mage.Sets/src/mage/cards/a/AegisOfTheLegion.java b/Mage.Sets/src/mage/cards/a/AegisOfTheLegion.java new file mode 100644 index 00000000000..da6c00b46af --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AegisOfTheLegion.java @@ -0,0 +1,93 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.MentorAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class AegisOfTheLegion extends CardImpl { + + public AegisOfTheLegion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{R}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+1 and has mentor. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect(new MentorAbility(), AttachmentType.EQUIPMENT) + .setText("and has mentor. (Whenever it attacks, put a +1/+1 counter on target attacking creature with lesser power.)")); + this.addAbility(ability); + + // Whenever equipped creature mentors a creature, put a shield counter on that creature. + this.addAbility(new AegisOfTheLegionTriggeredAbility()); + + // Equip {3} + this.addAbility(new EquipAbility(3, false)); + } + + private AegisOfTheLegion(final AegisOfTheLegion card) { + super(card); + } + + @Override + public AegisOfTheLegion copy() { + return new AegisOfTheLegion(this); + } +} + +class AegisOfTheLegionTriggeredAbility extends TriggeredAbilityImpl { + + AegisOfTheLegionTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.SHIELD.createInstance())); + setTriggerPhrase("Whenever equipped creature mentors a creature, "); + } + + private AegisOfTheLegionTriggeredAbility(final AegisOfTheLegionTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.MENTORED_CREATURE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + // 20240202 - 702.134c + // An ability that triggers whenever a creature mentors another creature + // triggers whenever a mentor ability whose source is the first creature and whose target is the second creature resolves. + Permanent attachment = getSourcePermanentOrLKI(game); + Permanent mentoredCreature = game.getPermanent(event.getTargetId()); + if (attachment == null || mentoredCreature == null || !event.getSourceId().equals(attachment.getAttachedTo())) { + return false; + } + + getEffects().setTargetPointer(new FixedTarget(mentoredCreature, game)); + return true; + } + + @Override + public AegisOfTheLegionTriggeredAbility copy() { + return new AegisOfTheLegionTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AerieBowmasters.java b/Mage.Sets/src/mage/cards/a/AerieBowmasters.java index 87059e1bcb2..3c4f76cb5fb 100644 --- a/Mage.Sets/src/mage/cards/a/AerieBowmasters.java +++ b/Mage.Sets/src/mage/cards/a/AerieBowmasters.java @@ -29,8 +29,6 @@ public final class AerieBowmasters extends CardImpl { // Megamorph {5}{G} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up at any time for its megamorph cost and put a +1/+1 counter on it.)) this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{5}{G}"), true)); - - } private AerieBowmasters(final AerieBowmasters card) { diff --git a/Mage.Sets/src/mage/cards/a/Aethertow.java b/Mage.Sets/src/mage/cards/a/Aethertow.java index ff79b20ea2e..7db15f9b34d 100644 --- a/Mage.Sets/src/mage/cards/a/Aethertow.java +++ b/Mage.Sets/src/mage/cards/a/Aethertow.java @@ -57,7 +57,7 @@ class AethertowEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (targetCreature != null) { return controller.putCardsOnTopOfLibrary(targetCreature, game, source, true); diff --git a/Mage.Sets/src/mage/cards/a/AgencyCoroner.java b/Mage.Sets/src/mage/cards/a/AgencyCoroner.java new file mode 100644 index 00000000000..e59bbd720c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AgencyCoroner.java @@ -0,0 +1,73 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class AgencyCoroner extends CardImpl { + + public AgencyCoroner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.OGRE); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(6); + + // {2}{B}, Sacrifice another creature: Draw a card. If the sacrificed creature was suspected, draw two cards instead. + Ability ability = new SimpleActivatedAbility(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(2), new DrawCardSourceControllerEffect(1), + AgencyCoronerCondition.instance, + "draw a card. If the sacrificed creature was suspected, draw two cards instead" + ), new ManaCostsImpl<>("{2}{B}")); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE)); + this.addAbility(ability); + + } + + private AgencyCoroner(final AgencyCoroner card) { + super(card); + } + + @Override + public AgencyCoroner copy() { + return new AgencyCoroner(this); + } +} + +enum AgencyCoronerCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil + .castStream(source.getCosts().stream(), SacrificeTargetCost.class) + .map(SacrificeTargetCost::getPermanents) + .flatMap(Collection::stream) + .anyMatch(Permanent::isSuspected); + } + + @Override + public String toString() { + return "the sacrificed creature was suspected"; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AgentFrankHorrigan.java b/Mage.Sets/src/mage/cards/a/AgentFrankHorrigan.java new file mode 100644 index 00000000000..8f8abee5938 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AgentFrankHorrigan.java @@ -0,0 +1,59 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.AttackedThisTurnSourceCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AgentFrankHorrigan extends CardImpl { + + public AgentFrankHorrigan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(8); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Agent Frank Horrigan has indestructible as long as it attacked this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), + AttackedThisTurnSourceCondition.instance, "{this} has indestructible as long as it attacked this turn" + ))); + + // Whenever Agent Frank Horrigan enters the battlefield or attacks, proliferate twice. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ProliferateEffect(false)); + ability.addEffect(new ProliferateEffect().setText(" twice")); + this.addAbility(ability); + } + + private AgentFrankHorrigan(final AgentFrankHorrigan card) { + super(card); + } + + @Override + public AgentFrankHorrigan copy() { + return new AgentFrankHorrigan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AggravatedAssault.java b/Mage.Sets/src/mage/cards/a/AggravatedAssault.java index f25b116e636..db32fbfd80a 100644 --- a/Mage.Sets/src/mage/cards/a/AggravatedAssault.java +++ b/Mage.Sets/src/mage/cards/a/AggravatedAssault.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -11,7 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -22,9 +21,8 @@ public final class AggravatedAssault extends CardImpl { public AggravatedAssault(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{R}"); - // {3}{R}{R}: Untap all creatures you control. After this main phase, there is an additional combat phase followed by an additional main phase. Activate this ability only any time you could cast a sorcery. - Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new UntapAllControllerEffect(new FilterControlledCreaturePermanent(), "Untap all creatures you control"), new ManaCostsImpl<>("{3}{R}{R}")); + Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, "Untap all creatures you control"), new ManaCostsImpl<>("{3}{R}{R}")); ability.addEffect(new AddCombatAndMainPhaseEffect()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java b/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java index 6941ebb3cd1..b0026ebfb44 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java +++ b/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java @@ -41,7 +41,9 @@ public final class AjaniAdversaryOfTyrants extends CardImpl { this.setStartingLoyalty(4); // +1: Put a +1/+1 counter on each of up to two target creatures. - Ability ability = new LoyaltyAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1); + Ability ability = new LoyaltyAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on each of up to two target creatures"), 1 + ); ability.addTarget(new TargetPermanent(0, 2, StaticFilters.FILTER_PERMANENT_CREATURES)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AjaniSteadfast.java b/Mage.Sets/src/mage/cards/a/AjaniSteadfast.java index 5efe32bdadf..b4b2ed40ac2 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniSteadfast.java +++ b/Mage.Sets/src/mage/cards/a/AjaniSteadfast.java @@ -19,7 +19,7 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterPlaneswalkerPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.command.emblems.AjaniSteadfastEmblem; @@ -62,7 +62,7 @@ public final class AjaniSteadfast extends CardImpl { this.addAbility(ability); // -2: Put a +1/+1 counter on each creature you control and a loyalty counter on each other planeswalker you control. - ability = new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()), -2); + ability = new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE), -2); effect = new AddCountersAllEffect(CounterType.LOYALTY.createInstance(), filter); effect.setText("and a loyalty counter on each other planeswalker you control"); ability.addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/a/AjaniUnyielding.java b/Mage.Sets/src/mage/cards/a/AjaniUnyielding.java index 97708026976..4ad32c3097d 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniUnyielding.java +++ b/Mage.Sets/src/mage/cards/a/AjaniUnyielding.java @@ -14,7 +14,7 @@ import mage.constants.SuperType; import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterPermanentCard; import mage.filter.common.FilterPlaneswalkerPermanent; import mage.filter.predicate.Predicates; @@ -51,7 +51,7 @@ public final class AjaniUnyielding extends CardImpl { this.addAbility(ajaniAbility2); // -9: Put five +1/+1 counters on each creature you control and five loyalty counters on each other planeswalker you control. - LoyaltyAbility ajaniAbility3 = new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(5), new FilterControlledCreaturePermanent()), -9); + LoyaltyAbility ajaniAbility3 = new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(5), StaticFilters.FILTER_CONTROLLED_CREATURE), -9); ajaniAbility3.addEffect(new AddCountersAllEffect(CounterType.LOYALTY.createInstance(5), planeswalkerFilter).setText("and five loyalty counters on each other planeswalker you control")); this.addAbility(ajaniAbility3); } diff --git a/Mage.Sets/src/mage/cards/a/AkkiBattleSquad.java b/Mage.Sets/src/mage/cards/a/AkkiBattleSquad.java index d6821e1eff3..befcf601a6f 100644 --- a/Mage.Sets/src/mage/cards/a/AkkiBattleSquad.java +++ b/Mage.Sets/src/mage/cards/a/AkkiBattleSquad.java @@ -38,7 +38,7 @@ public final class AkkiBattleSquad extends CardImpl { Ability ability = new AttacksCreatureYouControlTriggeredAbility( new UntapAllEffect(filter), false, filter ).setTriggerPhrase("Whenever one or more modified creatures you control attack, ").setTriggersOnceEachTurn(true); - ability.addEffect(new AdditionalCombatPhaseEffect()); + ability.addEffect(new AdditionalCombatPhaseEffect().setText("After this combat phase, there is an additional combat phase")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AkoumFlameseeker.java b/Mage.Sets/src/mage/cards/a/AkoumFlameseeker.java index 08d32eca21a..595b2239ced 100644 --- a/Mage.Sets/src/mage/cards/a/AkoumFlameseeker.java +++ b/Mage.Sets/src/mage/cards/a/AkoumFlameseeker.java @@ -1,22 +1,19 @@ - package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.Cards; -import mage.constants.*; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.game.Game; import mage.players.Player; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @@ -24,13 +21,6 @@ import mage.target.common.TargetControlledPermanent; */ public final class AkoumFlameseeker extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public AkoumFlameseeker(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); this.subtype.add(SubType.HUMAN); @@ -40,11 +30,7 @@ public final class AkoumFlameseeker extends CardImpl { this.toughness = new MageInt(2); // Cohort — {T}, Tap an untapped Ally you control: Discard a card. If you do, draw a card. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new AkoumFlameseekerEffect(), new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); - ability.setAbilityWord(AbilityWord.COHORT); - this.addAbility(ability); + this.addAbility(new CohortAbility(new AkoumFlameseekerEffect())); } private AkoumFlameseeker(final AkoumFlameseeker card) { diff --git a/Mage.Sets/src/mage/cards/a/AkroanHorse.java b/Mage.Sets/src/mage/cards/a/AkroanHorse.java index 2b627273b1f..e7926e5275d 100644 --- a/Mage.Sets/src/mage/cards/a/AkroanHorse.java +++ b/Mage.Sets/src/mage/cards/a/AkroanHorse.java @@ -112,11 +112,9 @@ class AkroanHorseGainControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent; - if (targetPointer == null) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { permanent = game.getPermanent(source.getFirstTarget()); - } else { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); } if (permanent == null) { return false; diff --git a/Mage.Sets/src/mage/cards/a/AkromasWill.java b/Mage.Sets/src/mage/cards/a/AkromasWill.java index 77ec2e648c2..f9ea1387a8d 100644 --- a/Mage.Sets/src/mage/cards/a/AkromasWill.java +++ b/Mage.Sets/src/mage/cards/a/AkromasWill.java @@ -31,7 +31,7 @@ public final class AkromasWill extends CardImpl { // Choose one. If you control a commander as you cast this spell, you may choose both. this.getSpellAbility().getModes().setChooseText( - "Choose one. If you control a commander as you cast this spell, you may choose both." + "Choose one. If you control a commander as you cast this spell, you may choose both instead." ); this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); diff --git a/Mage.Sets/src/mage/cards/a/AliveWell.java b/Mage.Sets/src/mage/cards/a/AliveWell.java index 7694010fe5d..59196d7507b 100644 --- a/Mage.Sets/src/mage/cards/a/AliveWell.java +++ b/Mage.Sets/src/mage/cards/a/AliveWell.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SpellAbilityType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.CentaurToken; import mage.players.Player; @@ -46,8 +45,6 @@ public final class AliveWell extends SplitCard { class WellEffect extends OneShotEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - public WellEffect() { super(Outcome.GainLife); staticText = "You gain 2 life for each creature you control"; @@ -66,7 +63,7 @@ class WellEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - int life = 2 * game.getBattlefield().count(filter, source.getControllerId(), source, game); + int life = 2 * game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), source, game); player.gainLife(life, game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/a/AllyEncampment.java b/Mage.Sets/src/mage/cards/a/AllyEncampment.java index b9c25241107..6912416ccae 100644 --- a/Mage.Sets/src/mage/cards/a/AllyEncampment.java +++ b/Mage.Sets/src/mage/cards/a/AllyEncampment.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -17,8 +16,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterSpell; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -26,10 +25,11 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class AllyEncampment extends CardImpl { - private static final FilterSpell FILTER = new FilterSpell("an Ally spell"); + private static final FilterSpell filterSpell = new FilterSpell("an Ally spell"); + private static final FilterControlledPermanent filterPermanent = new FilterControlledPermanent(SubType.ALLY, "Ally you control"); static { - FILTER.add(SubType.ALLY.getPredicate()); + filterSpell.add(SubType.ALLY.getPredicate()); } public AllyEncampment(UUID ownerId, CardSetInfo setInfo) { @@ -39,13 +39,13 @@ public final class AllyEncampment extends CardImpl { this.addAbility(new ColorlessManaAbility()); // {T} Add one mana of any color. Spend this mana only to cast an Ally spell. - this.addAbility(new ConditionalAnyColorManaAbility(new TapSourceCost(), 1, new ConditionalSpellManaBuilder(FILTER), true)); + this.addAbility(new ConditionalAnyColorManaAbility(new TapSourceCost(), 1, new ConditionalSpellManaBuilder(filterSpell), true)); // {1}, {T}, Sacrifice Ally Encampment: Return target Ally you control to its owner's hand. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new GenericManaCost(1)); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent(SubType.ALLY, "Ally you control"))); + ability.addTarget(new TargetControlledPermanent(filterPermanent)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AlmostPerfect.java b/Mage.Sets/src/mage/cards/a/AlmostPerfect.java new file mode 100644 index 00000000000..485ba555181 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlmostPerfect.java @@ -0,0 +1,53 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlmostPerfect extends CardImpl { + + public AlmostPerfect(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature has base power and toughness 9/10 and has indestructible. + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(9, 10)); + ability.addEffect(new GainAbilityAttachedEffect( + IndestructibleAbility.getInstance(), AttachmentType.AURA + ).setText("and has indestructible")); + this.addAbility(ability); + } + + private AlmostPerfect(final AlmostPerfect card) { + super(card); + } + + @Override + public AlmostPerfect copy() { + return new AlmostPerfect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlquistProftMasterSleuth.java b/Mage.Sets/src/mage/cards/a/AlquistProftMasterSleuth.java index 1962a83924d..40936d21dc4 100644 --- a/Mage.Sets/src/mage/cards/a/AlquistProftMasterSleuth.java +++ b/Mage.Sets/src/mage/cards/a/AlquistProftMasterSleuth.java @@ -17,7 +17,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -26,8 +26,6 @@ import java.util.UUID; */ public final class AlquistProftMasterSleuth extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CLUE, "a Clue"); - public AlquistProftMasterSleuth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{U}"); @@ -48,7 +46,7 @@ public final class AlquistProftMasterSleuth extends CardImpl { new DrawCardSourceControllerEffect(ManacostVariableValue.REGULAR, "you"), new ManaCostsImpl<>("{X}{W}{U}{U}") ); ability.addCost(new TapSourceCost()); - ability.addCost(new SacrificeTargetCost(filter)); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CLUE)); ability.addEffect(new GainLifeEffect(ManacostVariableValue.REGULAR).setText("and gain X life")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AmaliaBenavidesAguirre.java b/Mage.Sets/src/mage/cards/a/AmaliaBenavidesAguirre.java index 615b694504e..abe9a318d30 100644 --- a/Mage.Sets/src/mage/cards/a/AmaliaBenavidesAguirre.java +++ b/Mage.Sets/src/mage/cards/a/AmaliaBenavidesAguirre.java @@ -27,7 +27,7 @@ import java.util.UUID; */ public final class AmaliaBenavidesAguirre extends CardImpl { - private final static FilterPermanent filter = new FilterCreaturePermanent("other creatures"); + private static final FilterPermanent filter = new FilterCreaturePermanent("other creatures"); static { filter.add(AnotherPredicate.instance); @@ -50,7 +50,7 @@ public final class AmaliaBenavidesAguirre extends CardImpl { ability.addEffect(new ConditionalOneShotEffect( new DestroyAllEffect(filter), AmaliaBenavidesAguirreCondition.instance - ).setText("Then, destroy all other creatures if its power is exactly 20")); + ).setText("Then destroy all other creatures if its power is exactly 20")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AmphinMutineer.java b/Mage.Sets/src/mage/cards/a/AmphinMutineer.java index 2415b26aca3..b51e0ef02e7 100644 --- a/Mage.Sets/src/mage/cards/a/AmphinMutineer.java +++ b/Mage.Sets/src/mage/cards/a/AmphinMutineer.java @@ -17,7 +17,7 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.SalamnderWarriorToken; +import mage.game.permanent.token.SalamanderWarriorToken; import mage.players.Player; import mage.target.TargetPermanent; @@ -89,7 +89,7 @@ class AmphinMutineerEffect extends OneShotEffect { return false; } player.moveCards(permanent, Zone.EXILED, source, game); - new SalamnderWarriorToken().putOntoBattlefield(1, game, source, player.getId()); + new SalamanderWarriorToken().putOntoBattlefield(1, game, source, player.getId()); return true; } } diff --git a/Mage.Sets/src/mage/cards/a/AnaBattlemage.java b/Mage.Sets/src/mage/cards/a/AnaBattlemage.java index f52fe1b3285..d28bd70a201 100644 --- a/Mage.Sets/src/mage/cards/a/AnaBattlemage.java +++ b/Mage.Sets/src/mage/cards/a/AnaBattlemage.java @@ -87,7 +87,7 @@ class AnaBattlemageKickerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean applied = false; - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { applied = targetCreature.tap(source, game); Player controller = game.getPlayer(targetCreature.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/a/AncientRunes.java b/Mage.Sets/src/mage/cards/a/AncientRunes.java index 46c0d5fb413..4a7f4c98602 100644 --- a/Mage.Sets/src/mage/cards/a/AncientRunes.java +++ b/Mage.Sets/src/mage/cards/a/AncientRunes.java @@ -51,9 +51,9 @@ class AncientRunesDamageTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - int damage = game.getBattlefield().getAllActivePermanents(new FilterControlledArtifactPermanent("artifacts"), targetPointer.getFirst(game, source), game).size(); + int damage = game.getBattlefield().getAllActivePermanents(new FilterControlledArtifactPermanent("artifacts"), getTargetPointer().getFirst(game, source), game).size(); player.damage(damage, source.getSourceId(), source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java index 77b2c0e9d5a..80152ebea56 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java @@ -95,7 +95,7 @@ class AngelOfDestinyGainLifeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); int damage = (int) getValue("damage"); if (controller != null) { controller.gainLife(damage, game, source); diff --git a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java index b3b9532bbec..fa68bfd5821 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java @@ -55,7 +55,8 @@ public final class AngelOfSerenity extends CardImpl { this.addAbility(ability); // When Angel of Serenity leaves the battlefield, return the exiled cards to their owners' hands. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.HAND).withText(true, true), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.HAND) + .withText(true, true, false), false)); } private AngelOfSerenity(final AngelOfSerenity card) { diff --git a/Mage.Sets/src/mage/cards/a/AngelicReward.java b/Mage.Sets/src/mage/cards/a/AngelicReward.java index fa24705bea9..c3f8d47c1fa 100644 --- a/Mage.Sets/src/mage/cards/a/AngelicReward.java +++ b/Mage.Sets/src/mage/cards/a/AngelicReward.java @@ -1,6 +1,5 @@ package mage.cards.a; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; @@ -29,12 +28,12 @@ public final class AngelicReward extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets +3/+3 and has flying. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(3, 3, Duration.WhileOnBattlefield))); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.AURA))); + SimpleStaticAbility ability = new SimpleStaticAbility(new BoostEnchantedEffect(3, 3)); + ability.addEffect(new GainAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.AURA).setText("and has flying")); + this.addAbility(ability); } private AngelicReward(final AngelicReward card) { diff --git a/Mage.Sets/src/mage/cards/a/AnnihilatingFire.java b/Mage.Sets/src/mage/cards/a/AnnihilatingFire.java index 833ee900326..b63f62c637f 100644 --- a/Mage.Sets/src/mage/cards/a/AnnihilatingFire.java +++ b/Mage.Sets/src/mage/cards/a/AnnihilatingFire.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.cards.a; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java b/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java index 10a354ceae1..f67c0e38339 100644 --- a/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java +++ b/Mage.Sets/src/mage/cards/a/AnowonTheRuinThief.java @@ -72,7 +72,7 @@ class AnowonTheRuinThiefEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/AnvilOfBogardan.java b/Mage.Sets/src/mage/cards/a/AnvilOfBogardan.java index 7376ee06c04..116b67a7324 100644 --- a/Mage.Sets/src/mage/cards/a/AnvilOfBogardan.java +++ b/Mage.Sets/src/mage/cards/a/AnvilOfBogardan.java @@ -55,7 +55,7 @@ class AnvilOfBogardanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.drawCards(1, source, game); targetPlayer.discard(1, false, false, source, game); diff --git a/Mage.Sets/src/mage/cards/a/AnzragTheQuakeMole.java b/Mage.Sets/src/mage/cards/a/AnzragTheQuakeMole.java index 6f60bc9ba4f..35550291020 100644 --- a/Mage.Sets/src/mage/cards/a/AnzragTheQuakeMole.java +++ b/Mage.Sets/src/mage/cards/a/AnzragTheQuakeMole.java @@ -40,8 +40,7 @@ public final class AnzragTheQuakeMole extends CardImpl { Ability ability = new BecomesBlockedSourceTriggeredAbility( new UntapAllEffect(filter), false ); - ability.addEffect(new AdditionalCombatPhaseEffect() - .setText("After this combat phase, there is an additional combat phase")); + ability.addEffect(new AdditionalCombatPhaseEffect()); this.addAbility(ability); // {3}{R}{R}{G}{G}: Anzrag must be blocked each combat this turn if able. diff --git a/Mage.Sets/src/mage/cards/a/ApostlesBlessing.java b/Mage.Sets/src/mage/cards/a/ApostlesBlessing.java index a6ff5335b9f..1deca80d385 100644 --- a/Mage.Sets/src/mage/cards/a/ApostlesBlessing.java +++ b/Mage.Sets/src/mage/cards/a/ApostlesBlessing.java @@ -85,7 +85,7 @@ class ApostlesBlessingEffect extends OneShotEffect { protectionFilter.setMessage(choice.getChoice()); ProtectionAbility protectionAbility = new ProtectionAbility(protectionFilter); ContinuousEffect effect = new GainAbilityTargetEffect(protectionAbility, Duration.EndOfTurn); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/a/AquitectsWill.java b/Mage.Sets/src/mage/cards/a/AquitectsWill.java index c1f9df1e485..ebe43b05299 100644 --- a/Mage.Sets/src/mage/cards/a/AquitectsWill.java +++ b/Mage.Sets/src/mage/cards/a/AquitectsWill.java @@ -72,7 +72,7 @@ class AquitectsWillEffect extends BecomesBasicLandTargetEffect { @Override public boolean apply(Game game, Ability source) { - Permanent land = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent land = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (land == null || land.getCounters(game).getCount(CounterType.FLOOD) < 1) { discard(); return false; diff --git a/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java b/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java index 855b318d42a..c3fd6f9972d 100644 --- a/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java +++ b/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -16,16 +15,13 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCardInLibrary; -import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.common.*; /** * @@ -33,7 +29,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class ArachnusSpinner extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Spider you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Spider you control"); static { filter.add(SubType.SPIDER.getPredicate()); @@ -52,7 +48,7 @@ public final class ArachnusSpinner extends CardImpl { // Tap an untapped Spider you control: Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle it. SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ArachnusSpinnerEffect(), - new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false))); + new TapTargetCost(new TargetControlledPermanent(1, 1, filter, false))); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AragornHornburgHero.java b/Mage.Sets/src/mage/cards/a/AragornHornburgHero.java index 1d9c5d3a2dd..7ac9ebc21c3 100644 --- a/Mage.Sets/src/mage/cards/a/AragornHornburgHero.java +++ b/Mage.Sets/src/mage/cards/a/AragornHornburgHero.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.RenownAbility; @@ -16,8 +16,6 @@ import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.permanent.RenownedPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; import java.util.UUID; @@ -54,7 +52,7 @@ public final class AragornHornburgHero extends CardImpl { this.addAbility(ability); // Whenever a renowned creature you control deals combat damage to a player, double the number of +1/+1 counters on it. this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( - new AragornDoubleCountersTargetEffect(), filter, + new DoubleCountersTargetEffect(CounterType.P1P1), filter, false, SetTargetPointer.PERMANENT, true )); @@ -69,28 +67,3 @@ public final class AragornHornburgHero extends CardImpl { return new AragornHornburgHero(this); } } -//Copied from Elvish Vatkeeper -class AragornDoubleCountersTargetEffect extends OneShotEffect { - - AragornDoubleCountersTargetEffect() { - super(Outcome.Benefit); - staticText = "double the number of +1/+1 counters on it"; - } - - private AragornDoubleCountersTargetEffect(final AragornDoubleCountersTargetEffect effect) { - super(effect); - } - - @Override - public AragornDoubleCountersTargetEffect copy() { - return new AragornDoubleCountersTargetEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - return permanent != null && permanent.addCounters(CounterType.P1P1.createInstance( - permanent.getCounters(game).getCount(CounterType.P1P1) - ), source.getControllerId(), source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/a/ArahboRoarOfTheWorld.java b/Mage.Sets/src/mage/cards/a/ArahboRoarOfTheWorld.java index 83a837a2b4f..44d05f59024 100644 --- a/Mage.Sets/src/mage/cards/a/ArahboRoarOfTheWorld.java +++ b/Mage.Sets/src/mage/cards/a/ArahboRoarOfTheWorld.java @@ -97,7 +97,7 @@ class ArahboEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature != null && creature.isCreature(game)) { int pow = creature.getPower().getValue(); ContinuousEffect effect = new BoostTargetEffect(pow, pow, Duration.EndOfTurn); diff --git a/Mage.Sets/src/mage/cards/a/AraumiOfTheDeadTide.java b/Mage.Sets/src/mage/cards/a/AraumiOfTheDeadTide.java index 8f1cf5a4f2f..271610b34ae 100644 --- a/Mage.Sets/src/mage/cards/a/AraumiOfTheDeadTide.java +++ b/Mage.Sets/src/mage/cards/a/AraumiOfTheDeadTide.java @@ -68,7 +68,7 @@ class AraumiOfTheDeadTideEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/ArborealGrazer.java b/Mage.Sets/src/mage/cards/a/ArborealGrazer.java index 445bf3daac8..9ad7a83ccb6 100644 --- a/Mage.Sets/src/mage/cards/a/ArborealGrazer.java +++ b/Mage.Sets/src/mage/cards/a/ArborealGrazer.java @@ -20,7 +20,7 @@ public final class ArborealGrazer extends CardImpl { public ArborealGrazer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); - this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.SLOTH, SubType.BEAST); this.power = new MageInt(0); this.toughness = new MageInt(3); diff --git a/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java b/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java index da8abdf3155..cf60947b69f 100644 --- a/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java +++ b/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java @@ -11,13 +11,11 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSacrifice; import java.util.UUID; @@ -27,11 +25,10 @@ import java.util.UUID; */ public final class ArchdemonOfGreed extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Human"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Human"); static { filter.add(SubType.HUMAN.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); } public ArchdemonOfGreed(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java b/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java new file mode 100644 index 00000000000..a50c5b90541 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java @@ -0,0 +1,155 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.SearchEffect; +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +/** + * + * @author jimga150 + */ +public final class ArchdruidsCharm extends CardImpl { + + private static final FilterCard creatureOrLandFilter = new FilterCard("creature or land card"); + + static { + creatureOrLandFilter.add(Predicates.or(CardType.CREATURE.getPredicate(), CardType.LAND.getPredicate())); + } + + public ArchdruidsCharm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}{G}{G}"); + + + // Choose one -- + // * Search your library for a creature or land card and reveal it. Put it onto the battlefield tapped if it's a land card. Otherwise, put it into your hand. Then shuffle. + this.getSpellAbility().addEffect(new ArchdruidsCharmMode1Effect(new TargetCardInLibrary(creatureOrLandFilter), true, new FilterLandCard())); + + // * Put a +1/+1 counter on target creature you control. It deals damage equal to its power to target creature you don't control. + // Based on Aggressive Instinct + Mode mode2 = new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + mode2.addTarget(new TargetControlledCreaturePermanent()); + mode2.addEffect(new DamageWithPowerFromOneToAnotherTargetEffect("it")); + mode2.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + this.getSpellAbility().addMode(mode2); + + // * Exile target artifact or enchantment. + // Based on Altar's Light + Mode mode3 = new Mode(new ExileTargetEffect()); + mode3.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.getSpellAbility().addMode(mode3); + } + + private ArchdruidsCharm(final ArchdruidsCharm card) { + super(card); + } + + @Override + public ArchdruidsCharm copy() { + return new ArchdruidsCharm(this); + } +} + +// Based on SearchLibraryPutInHandOrOnBattlefieldEffect +class ArchdruidsCharmMode1Effect extends SearchEffect { + + private final boolean revealCards; + private final FilterCard putOnBattlefieldFilter; + + public ArchdruidsCharmMode1Effect(TargetCardInLibrary target, boolean revealCards, FilterCard putOnBattlefieldFilter) { + super(target, Outcome.DrawCard); + this.revealCards = revealCards; + this.putOnBattlefieldFilter = putOnBattlefieldFilter; + setText(); + } + + protected ArchdruidsCharmMode1Effect(final ArchdruidsCharmMode1Effect effect) { + super(effect); + this.revealCards = effect.revealCards; + this.putOnBattlefieldFilter = effect.putOnBattlefieldFilter; + } + + @Override + public ArchdruidsCharmMode1Effect copy() { + return new ArchdruidsCharmMode1Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + target.clearChosen(); + + boolean searchSuccessful = false; + + if (controller.searchLibrary(target, source, game)) { + if (!target.getTargets().isEmpty()) { + Cards cards = new CardsImpl(); + for (UUID cardId : target.getTargets()) { + Card card = game.getCard(cardId); + if (card == null) { + continue; + } + if (putOnBattlefieldFilter.match(card, game)) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); + } else { + controller.moveCards(card, Zone.HAND, source, game); + } + cards.add(card); + } + if (revealCards) { + String name = "Reveal"; + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null) { + name = sourceCard.getIdName(); + } + controller.revealCards(name, cards, game); + } + } + searchSuccessful = true; + } + controller.shuffleLibrary(source, game); + return searchSuccessful; + } + + private void setText() { + StringBuilder sb = new StringBuilder(); + sb.append("Search your library for "); + if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { + sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); + sb.append(target.getTargetName()).append(revealCards ? " and reveal them." : "."); + } else { + sb.append("a ").append(target.getTargetName()).append(revealCards ? " and reveal it." : "."); + } + if (putOnBattlefieldFilter != null) { + sb.append(" Put it onto the battlefield tapped if it's a "); + sb.append(putOnBattlefieldFilter.getMessage()); + sb.append(". Otherwise, put it into your hand."); + } + sb.append(" Then shuffle."); + staticText = sb.toString(); + } + +} diff --git a/Mage.Sets/src/mage/cards/a/ArcumsSleigh.java b/Mage.Sets/src/mage/cards/a/ArcumsSleigh.java new file mode 100644 index 00000000000..8601c8c0fe5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArcumsSleigh.java @@ -0,0 +1,59 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.common.DefendingPlayerControlsNoSourceCondition; +import mage.abilities.condition.common.IsPhaseCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.TurnPhase; +import mage.filter.common.FilterLandPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class ArcumsSleigh extends CardImpl { + + private static final FilterLandPermanent filter = new FilterLandPermanent(); + + static { + filter.add(SuperType.SNOW.getPredicate()); + } + + public ArcumsSleigh(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {2}, {tap}: Target creature gains vigilance until end of turn. + // Activate only during combat and only if defending player controls a snow land. + CompoundCondition condition = new CompoundCondition( + "during combat and only if defending player controls a snow land", + new IsPhaseCondition(TurnPhase.COMBAT, false), // Only during combat + new DefendingPlayerControlsNoSourceCondition(filter) // Only if defending player controls a snow land + ); + + Ability ability = new ConditionalActivatedAbility( + new GainAbilityTargetEffect(VigilanceAbility.getInstance()), new GenericManaCost(2), condition); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private ArcumsSleigh(final ArcumsSleigh card) { + super(card); + } + + @Override + public ArcumsSleigh copy() { + return new ArcumsSleigh(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArmedWithProof.java b/Mage.Sets/src/mage/cards/a/ArmedWithProof.java new file mode 100644 index 00000000000..ab20f652591 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArmedWithProof.java @@ -0,0 +1,93 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class ArmedWithProof extends CardImpl { + + public ArmedWithProof(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // When Armed with Proof enters the battlefield, investigate twice. + this.addAbility(new EntersBattlefieldTriggeredAbility(new InvestigateEffect(2))); + + // Clues you control are Equipment in addition to their other types and have "Equipped creature gets +2/+0" and equip {2}. + this.addAbility(new SimpleStaticAbility(new ArmedWithProofEffect())); + } + + private ArmedWithProof(final ArmedWithProof card) { + super(card); + } + + @Override + public ArmedWithProof copy() { + return new ArmedWithProof(this); + } +} + +class ArmedWithProofEffect extends ContinuousEffectImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.CLUE); + + ArmedWithProofEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Clues you control are Equipment in addition to their other " + + "types and have \"Equipped creature gets +2/+0\" and equip {2}."; + } + + private ArmedWithProofEffect(final ArmedWithProofEffect effect) { + super(effect); + } + + @Override + public ArmedWithProofEffect copy() { + return new ArmedWithProofEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source, game + )) { + switch (layer) { + case TypeChangingEffects_4: + permanent.addSubType(game, SubType.EQUIPMENT); + break; + case AbilityAddingRemovingEffects_6: + permanent.addAbility(new SimpleStaticAbility( + new BoostEquippedEffect(2, 0) + ), source.getSourceId(), game); + permanent.addAbility(new EquipAbility(2), source.getSourceId(), game); + break; + } + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.TypeChangingEffects_4 || layer == Layer.AbilityAddingRemovingEffects_6; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArmoryPaladin.java b/Mage.Sets/src/mage/cards/a/ArmoryPaladin.java new file mode 100644 index 00000000000..4eea10a8caa --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArmoryPaladin.java @@ -0,0 +1,58 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArmoryPaladin extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an Aura or Equipment spell"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + } + + public ArmoryPaladin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you cast an Aura or Equipment spell, exile the top card of your library. You may play that card until the end of your next turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn) + .withTextOptions("that card", true), + filter, false + )); + } + + private ArmoryPaladin(final ArmoryPaladin card) { + super(card); + } + + @Override + public ArmoryPaladin copy() { + return new ArmoryPaladin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArmsDealer.java b/Mage.Sets/src/mage/cards/a/ArmsDealer.java index 652e00bb8a4..0b7831a4c5b 100644 --- a/Mage.Sets/src/mage/cards/a/ArmsDealer.java +++ b/Mage.Sets/src/mage/cards/a/ArmsDealer.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -12,8 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -22,11 +20,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class ArmsDealer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GOBLIN, "Goblin"); public ArmsDealer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); diff --git a/Mage.Sets/src/mage/cards/a/AshenmoorCohort.java b/Mage.Sets/src/mage/cards/a/AshenmoorCohort.java index 6a3987ad6da..778edcdb755 100644 --- a/Mage.Sets/src/mage/cards/a/AshenmoorCohort.java +++ b/Mage.Sets/src/mage/cards/a/AshenmoorCohort.java @@ -1,4 +1,3 @@ - package mage.cards.a; import mage.MageInt; @@ -32,7 +31,7 @@ public final class AshenmoorCohort extends CardImpl { filter.add(AnotherPredicate.instance); } - private static final String rule = "Ashenmoor Cohort gets +1/+1 as long as you control another black creature"; + private static final String rule = "{this} gets +1/+1 as long as you control another black creature"; public AshenmoorCohort(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}"); diff --git a/Mage.Sets/src/mage/cards/a/AshiokSculptorOfFears.java b/Mage.Sets/src/mage/cards/a/AshiokSculptorOfFears.java index 8de002f7e59..361f63b61e9 100644 --- a/Mage.Sets/src/mage/cards/a/AshiokSculptorOfFears.java +++ b/Mage.Sets/src/mage/cards/a/AshiokSculptorOfFears.java @@ -10,9 +10,8 @@ import mage.abilities.effects.common.continuous.GainControlAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; @@ -26,8 +25,6 @@ import java.util.UUID; */ public final class AshiokSculptorOfFears extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public AshiokSculptorOfFears(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{U}{B}"); @@ -45,7 +42,7 @@ public final class AshiokSculptorOfFears extends CardImpl { // −5: Put target creature card from a graveyard onto the battlefield under your control. ability = new LoyaltyAbility(new ReturnFromGraveyardToBattlefieldTargetEffect() .setText("put target creature card from a graveyard onto the battlefield under your control"), -5); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); // −11: Gain control of all creatures target opponent controls. diff --git a/Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java b/Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java index b8af22339c3..34476d6c6df 100644 --- a/Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java +++ b/Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java @@ -97,7 +97,7 @@ class AshiokWickedManipulatorReplacementEffect extends ReplacementEffectImpl { } Set cards = player.getLibrary().getTopCards(game, event.getAmount()); - player.moveCardsToExile(cards, source, game, false, null, ""); + player.moveCardsToExile(cards, source, game, true, null, ""); return true; } diff --git a/Mage.Sets/src/mage/cards/a/AssaultronDominator.java b/Mage.Sets/src/mage/cards/a/AssaultronDominator.java new file mode 100644 index 00000000000..d0e0a6643b7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AssaultronDominator.java @@ -0,0 +1,113 @@ +package mage.cards.a; + +import java.util.*; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * @author Cguy7777 + */ +public final class AssaultronDominator extends CardImpl { + + private static final FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent("artifact creature you control"); + + static { + filter.add(CardType.ARTIFACT.getPredicate()); + } + + public AssaultronDominator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Assaultron Dominator enters the battlefield, you get {E}{E}. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2))); + + // Whenever an artifact creature you control attacks, you may pay {E}. If you do, put your choice of a +1/+1, first strike, or trample counter on that creature. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility( + new DoIfCostPaid(new AssaultronDominatorEffect(), new PayEnergyCost(1)), false, filter, true)); + } + + private AssaultronDominator(final AssaultronDominator card) { + super(card); + } + + @Override + public AssaultronDominator copy() { + return new AssaultronDominator(this); + } +} + +class AssaultronDominatorEffect extends OneShotEffect { + + private static final Set choices = new LinkedHashSet<>(Arrays.asList("+1/+1", "First strike", "Trample")); + + AssaultronDominatorEffect() { + super(Outcome.BoostCreature); + staticText = "put your choice of a +1/+1, first strike, or trample counter on that creature"; + } + + protected AssaultronDominatorEffect(AssaultronDominatorEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || creature == null) { + return false; + } + + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose +1/+1, first strike, or trample counter"); + choice.setChoices(choices); + player.choose(outcome, choice, game); + + String chosen = choice.getChoice(); + + CounterType counterType; + switch (chosen) { + case "+1/+1": + counterType = CounterType.P1P1; + break; + case "First strike": + counterType = CounterType.FIRST_STRIKE; + break; + case "Trample": + counterType = CounterType.TRAMPLE; + break; + default: + return false; + } + + return creature.addCounters(counterType.createInstance(), source, game); + } + + @Override + public AssaultronDominatorEffect copy() { + return new AssaultronDominatorEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java b/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java new file mode 100644 index 00000000000..18b2dddf2d3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java @@ -0,0 +1,159 @@ +package mage.cards.a; + +import mage.MageIdentifier; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.hint.Hint; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author xenohedron + */ +public final class AssembleThePlayers extends CardImpl { + + public AssembleThePlayers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // Once each turn, you may cast a creature spell with power 2 or less from the top of your library. + this.addAbility( + new SimpleStaticAbility(new AssembleThePlayersPlayTopEffect()) + .setIdentifier(MageIdentifier.AssembleThePlayersWatcher) + .addHint(AssembleThePlayersHint.instance), + new AssembleThePlayersWatcher() + // all based on Johann, Apprentice Sorcerer + ); + + } + + private AssembleThePlayers(final AssembleThePlayers card) { + super(card); + } + + @Override + public AssembleThePlayers copy() { + return new AssembleThePlayers(this); + } +} + +enum AssembleThePlayersHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + AssembleThePlayersWatcher watcher = game.getState().getWatcher(AssembleThePlayersWatcher.class); + if (watcher != null) { + boolean used = watcher.isAbilityUsed(ability.getControllerId(), new MageObjectReference(ability.getSourceId(), game)); + if (used) { + Player player = game.getPlayer(ability.getControllerId()); + if (player != null) { + return "A spell has been cast by " + player.getLogName() + " with {this} this turn."; + } + } + } + return ""; + } + + @Override + public AssembleThePlayersHint copy() { + return this; + } +} + +class AssembleThePlayersPlayTopEffect extends AsThoughEffectImpl { + + AssembleThePlayersPlayTopEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Once each turn, you may cast a creature spell with power 2 or less from the top of your library"; + } + + private AssembleThePlayersPlayTopEffect(final AssembleThePlayersPlayTopEffect effect) { + super(effect); + } + + @Override + public AssembleThePlayersPlayTopEffect copy() { + return new AssembleThePlayersPlayTopEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + // Only applies for the controller of the ability. + if (!affectedControllerId.equals(source.getControllerId())) { + return false; + } + + Player controller = game.getPlayer(source.getControllerId()); + AssembleThePlayersWatcher watcher = game.getState().getWatcher(AssembleThePlayersWatcher.class); + Permanent sourceObject = game.getPermanent(source.getSourceId()); + if (controller == null || watcher == null || sourceObject == null) { + return false; + } + + // Has the ability already been used this turn by the player? + if (watcher.isAbilityUsed(controller.getId(), new MageObjectReference(sourceObject, game))) { + return false; + } + + Card card = game.getCard(objectId); + Card topCard = controller.getLibrary().getFromTop(game); + // Is the card attempted to be played the top card of the library? + if (card == null || topCard == null || !topCard.getId().equals(card.getMainCard().getId())) { + return false; + } + + // Only works for creatures with power 2 or less + return card.isCreature(game) && card.getPower().getValue() <=2; + } +} + +class AssembleThePlayersWatcher extends Watcher { + + // player -> set of all permanent's mor that already used their once per turn Approval. + private final Map> usedFrom = new HashMap<>(); + + public AssembleThePlayersWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + UUID playerId = event.getPlayerId(); + if (event.getType() == GameEvent.EventType.SPELL_CAST + && event.hasApprovingIdentifier(MageIdentifier.AssembleThePlayersWatcher) + && playerId != null) { + usedFrom.computeIfAbsent(playerId, k -> new HashSet<>()) + .add(event.getAdditionalReference().getApprovingMageObjectReference()); + } + } + + @Override + public void reset() { + super.reset(); + usedFrom.clear(); + } + + public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) { + return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AssemblyHall.java b/Mage.Sets/src/mage/cards/a/AssemblyHall.java index 55d37ab8d5b..8cdd37df5b8 100644 --- a/Mage.Sets/src/mage/cards/a/AssemblyHall.java +++ b/Mage.Sets/src/mage/cards/a/AssemblyHall.java @@ -53,9 +53,9 @@ class AssemblyHallEffect extends OneShotEffect { AssemblyHallEffect() { super(Outcome.Benefit); - this.staticText = "reveal a creature card from your hand. " + this.staticText = "reveal a creature card in your hand. " + "Search your library for a card with the same name as that card, " - + "reveal it, and put it into your hand. Then shuffle"; + + "reveal it, put it into your hand, then shuffle"; } private AssemblyHallEffect(final AssemblyHallEffect effect) { diff --git a/Mage.Sets/src/mage/cards/a/AstarionTheDecadent.java b/Mage.Sets/src/mage/cards/a/AstarionTheDecadent.java index 239d75dac22..273d44e66cf 100644 --- a/Mage.Sets/src/mage/cards/a/AstarionTheDecadent.java +++ b/Mage.Sets/src/mage/cards/a/AstarionTheDecadent.java @@ -51,14 +51,17 @@ public final class AstarionTheDecadent extends CardImpl { // At the beginning of your end step, choose one — // • Feed — Target opponent loses life equal to the amount of life they lost this turn. Ability ability = new BeginningOfEndStepTriggeredAbility( - new LoseLifeTargetEffect(AstarionTheDecadentValue.instance), TargetController.YOU, false + new LoseLifeTargetEffect(AstarionTheDecadentValue.instance) + .setText("target opponent loses life equal to the amount of life they lost this turn"), + TargetController.YOU, false ); ability.addTarget(new TargetOpponent()); ability.withFirstModeFlavorWord("Feed"); ability.addHint(ControllerGainedLifeCount.getHint()); // • Friends — You gain life equal to the amount of life you gained this turn. - ability.addMode(new Mode(new GainLifeEffect(ControllerGainedLifeCount.instance)).withFlavorWord("Friends")); + ability.addMode(new Mode(new GainLifeEffect(ControllerGainedLifeCount.instance) + .setText("you gain life equal to the amount of life you gained this turn")).withFlavorWord("Friends")); this.addAbility(ability.addHint(AstarionTheDecadentHint.instance), new PlayerGainedLifeWatcher()); } diff --git a/Mage.Sets/src/mage/cards/a/AtemsisAllSeeing.java b/Mage.Sets/src/mage/cards/a/AtemsisAllSeeing.java index 31c071e71b0..10a1e54e688 100644 --- a/Mage.Sets/src/mage/cards/a/AtemsisAllSeeing.java +++ b/Mage.Sets/src/mage/cards/a/AtemsisAllSeeing.java @@ -80,7 +80,7 @@ class AtemsisAllSeeingEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/Atomize.java b/Mage.Sets/src/mage/cards/a/Atomize.java new file mode 100644 index 00000000000..1a67d3601b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/Atomize.java @@ -0,0 +1,34 @@ +package mage.cards.a; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Atomize extends CardImpl { + + public Atomize(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}{G}"); + + // Destroy target nonland permanent. Proliferate. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + this.getSpellAbility().addEffect(new ProliferateEffect()); + } + + private Atomize(final Atomize card) { + super(card); + } + + @Override + public Atomize copy() { + return new Atomize(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AuraGraft.java b/Mage.Sets/src/mage/cards/a/AuraGraft.java index 6c3fcb64e73..9410f5860da 100644 --- a/Mage.Sets/src/mage/cards/a/AuraGraft.java +++ b/Mage.Sets/src/mage/cards/a/AuraGraft.java @@ -101,7 +101,7 @@ class AuraGraftMoveAuraEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent enchantment = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent enchantment = game.getPermanent(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (enchantment == null || controller == null) { return false; diff --git a/Mage.Sets/src/mage/cards/a/AuriokReplica.java b/Mage.Sets/src/mage/cards/a/AuriokReplica.java index b05a266d472..8ff9dcaeb0f 100644 --- a/Mage.Sets/src/mage/cards/a/AuriokReplica.java +++ b/Mage.Sets/src/mage/cards/a/AuriokReplica.java @@ -64,8 +64,8 @@ class AuriokReplicaEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/a/AuriokSurvivors.java b/Mage.Sets/src/mage/cards/a/AuriokSurvivors.java index d0c53a1667a..67dd573d74b 100644 --- a/Mage.Sets/src/mage/cards/a/AuriokSurvivors.java +++ b/Mage.Sets/src/mage/cards/a/AuriokSurvivors.java @@ -66,7 +66,7 @@ class AuriokSurvivorsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent p = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent p = game.getPermanent(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); Player player = game.getPlayer(source.getControllerId()); if (p != null && player != null && sourcePermanent != null) { diff --git a/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java b/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java new file mode 100644 index 00000000000..6d7d037d2e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AutomatedAssemblyLine.java @@ -0,0 +1,42 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.RobotToken; + +/** + * @author Cguy7777 + */ +public final class AutomatedAssemblyLine extends CardImpl { + + public AutomatedAssemblyLine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + // Whenever one or more artifact creatures you control deal combat damage to a player, you get {E}. + this.addAbility(new DealCombatDamageControlledTriggeredAbility( + new GetEnergyCountersControllerEffect(1), StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE)); + + // Pay {E}{E}{E}: Create a tapped 3/3 colorless Robot artifact creature token. + this.addAbility(new SimpleActivatedAbility( + new CreateTokenEffect(new RobotToken(), 1, true), + new PayEnergyCost(3))); + } + + private AutomatedAssemblyLine(final AutomatedAssemblyLine card) { + super(card); + } + + @Override + public AutomatedAssemblyLine copy() { + return new AutomatedAssemblyLine(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AvariceAmulet.java b/Mage.Sets/src/mage/cards/a/AvariceAmulet.java index a372b29d524..123d5f8cf6a 100644 --- a/Mage.Sets/src/mage/cards/a/AvariceAmulet.java +++ b/Mage.Sets/src/mage/cards/a/AvariceAmulet.java @@ -1,15 +1,13 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.EquipAbility; @@ -17,10 +15,10 @@ import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author emerald000 @@ -46,7 +44,7 @@ public final class AvariceAmulet extends CardImpl { this.addAbility(ability); // Whenever equipped creature dies, target opponent gains control of Avarice Amulet. - ability = new DiesAttachedTriggeredAbility(new AvariceAmuletChangeControlEffect(), "equipped creature", false); + ability = new DiesAttachedTriggeredAbility(new TargetPlayerGainControlSourceEffect(), "equipped creature", false); ability.addTarget(new TargetOpponent()); this.addAbility(ability); @@ -63,30 +61,3 @@ public final class AvariceAmulet extends CardImpl { return new AvariceAmulet(this); } } - -class AvariceAmuletChangeControlEffect extends ContinuousEffectImpl { - - AvariceAmuletChangeControlEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "target opponent gains control of {this}"; - } - - private AvariceAmuletChangeControlEffect(final AvariceAmuletChangeControlEffect effect) { - super(effect); - } - - @Override - public AvariceAmuletChangeControlEffect copy() { - return new AvariceAmuletChangeControlEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/a/AzraBladeseeker.java b/Mage.Sets/src/mage/cards/a/AzraBladeseeker.java index e88d4fbd397..955facafbc1 100644 --- a/Mage.Sets/src/mage/cards/a/AzraBladeseeker.java +++ b/Mage.Sets/src/mage/cards/a/AzraBladeseeker.java @@ -90,7 +90,7 @@ class AzraBladeseekerEffect extends OneShotEffect { return true; } - class PlayerCard { + static class PlayerCard { private final Player player; private final Card card; diff --git a/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java b/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java index 712d08a1044..cffd0f25c49 100644 --- a/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java +++ b/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java @@ -14,6 +14,7 @@ import mage.constants.*; import mage.game.Game; import mage.players.Player; +import java.util.Objects; import java.util.UUID; /** @@ -74,8 +75,9 @@ class BackdraftHellkiteEffect extends ContinuousEffectImpl { } player.getGraveyard() .stream() - .map((cardId) -> game.getCard(cardId)) - .filter(card1 -> card1.isInstantOrSorcery(game)) + .map(game::getCard) + .filter(Objects::nonNull) + .filter(card -> card.isInstantOrSorcery(game)) .forEachOrdered(card -> affectedObjectList.add(new MageObjectReference(card, game))); } diff --git a/Mage.Sets/src/mage/cards/b/Backlash.java b/Mage.Sets/src/mage/cards/b/Backlash.java index 9827ef1ceb5..526ad3e8ede 100644 --- a/Mage.Sets/src/mage/cards/b/Backlash.java +++ b/Mage.Sets/src/mage/cards/b/Backlash.java @@ -62,7 +62,7 @@ class BacklashEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { targetCreature.tap(source, game); Player controller = game.getPlayer(targetCreature.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java index 377e76535b1..3d82c25bfd4 100644 --- a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java +++ b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterAttackingCreature; import mage.filter.common.FilterBlockingCreature; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.BlockerDeclaredEvent; @@ -111,7 +111,7 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect { Player targetsController = game.getPlayer(permanent.getControllerId()); if (targetsController != null) { FilterAttackingCreature filter = new FilterAttackingCreature("creature attacking " + targetsController.getLogName()); - filter.add(new PermanentInListPredicate(list)); + filter.add(new PermanentReferenceInCollectionPredicate(list, game)); TargetPermanent target = new TargetPermanent(filter); target.withNotTarget(true); if (target.canChoose(controller.getId(), source, game)) { diff --git a/Mage.Sets/src/mage/cards/b/BalothPackhunter.java b/Mage.Sets/src/mage/cards/b/BalothPackhunter.java new file mode 100644 index 00000000000..bae2dd7cbd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalothPackhunter.java @@ -0,0 +1,56 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author tiera3 - modified from CharmedStray + */ +public final class BalothPackhunter extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("other creature you control named Baloth Packhunter"); + + static { + filter.add(new NamePredicate("Baloth Packhunter")); + filter.add(AnotherPredicate.instance); + } + + public BalothPackhunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Baloth Packhunter enters the battlefield, put two +1/+1 counters on each other creature you control named Baloth Packhunter. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(2), filter) + )); + } + + private BalothPackhunter(final BalothPackhunter card) { + super(card); + } + + @Override + public BalothPackhunter copy() { + return new BalothPackhunter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BalshanBeguiler.java b/Mage.Sets/src/mage/cards/b/BalshanBeguiler.java index 547a63552ea..8a371ff5329 100644 --- a/Mage.Sets/src/mage/cards/b/BalshanBeguiler.java +++ b/Mage.Sets/src/mage/cards/b/BalshanBeguiler.java @@ -66,7 +66,7 @@ class BalshanBeguilerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player you = game.getPlayer(source.getControllerId()); if (player == null || you == null) { return false; diff --git a/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java b/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java index ba2509f0d0e..9223387c3ee 100644 --- a/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java +++ b/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java @@ -64,7 +64,7 @@ public final class BaneAlleyBroker extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(3); - // {tap}: Draw a card, then exile a card from your hand face down. + // {T}: Draw a card, then exile a card from your hand face down. this.addAbility(new SimpleActivatedAbility(new BaneAlleyBrokerDrawExileEffect(), new TapSourceCost())); // You may look at cards exiled with Bane Alley Broker. diff --git a/Mage.Sets/src/mage/cards/b/BanefulOmen.java b/Mage.Sets/src/mage/cards/b/BanefulOmen.java index 2c01f4bed15..881b602b313 100644 --- a/Mage.Sets/src/mage/cards/b/BanefulOmen.java +++ b/Mage.Sets/src/mage/cards/b/BanefulOmen.java @@ -1,18 +1,14 @@ - package mage.cards.b; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; -import java.util.Set; import java.util.UUID; /** @@ -24,7 +20,7 @@ public final class BanefulOmen extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}{B}{B}"); // At the beginning of your end step, you may reveal the top card of your library. If you do, each opponent loses life equal to that card's converted mana cost. - this.addAbility(new BanefulOmenTriggeredAbility()); + this.addAbility(new BeginningOfYourEndStepTriggeredAbility(new BanefulOmenEffect(), true)); } private BanefulOmen(final BanefulOmen card) { @@ -36,78 +32,43 @@ public final class BanefulOmen extends CardImpl { return new BanefulOmen(this); } - class BanefulOmenTriggeredAbility extends TriggeredAbilityImpl { +} - BanefulOmenTriggeredAbility() { - super(Zone.BATTLEFIELD, new BanefulOmenEffect(), true); - } +class BanefulOmenEffect extends OneShotEffect { - private BanefulOmenTriggeredAbility(BanefulOmenTriggeredAbility ability) { - super(ability); - } - - @Override - public BanefulOmenTriggeredAbility copy() { - return new BanefulOmenTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.controllerId); - } - - @Override - public String getRule() { - return "At the beginning of your end step, you may reveal the top card of your library. If you do, each opponent loses life equal to that card's mana value."; - } + BanefulOmenEffect() { + super(Outcome.Benefit); + staticText = "reveal the top card of your library. If you do, each opponent loses life equal to that card's mana value"; } - static class BanefulOmenEffect extends OneShotEffect { + private BanefulOmenEffect(final BanefulOmenEffect effect) { + super(effect); + } - BanefulOmenEffect() { - super(Outcome.Benefit); + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || !player.getLibrary().hasCards()) { + return false; } - - private BanefulOmenEffect(final BanefulOmenEffect effect) { - super(effect); + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; } + player.revealCards("Baneful Omen", new CardsImpl(card), game); - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; + int mv = card.getManaValue(); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null) { + opponent.loseLife(mv, game, source, false); } - if (!player.getLibrary().hasCards()) { - return false; - } - Card card = player.getLibrary().getFromTop(game); - if (card == null) { - return false; - } - Cards cards = new CardsImpl(card); - player.revealCards("Baneful Omen", cards, game); - - - int loseLife = card.getManaValue(); - Set opponents = game.getOpponents(source.getControllerId()); - for (UUID opponentUuid : opponents) { - Player opponent = game.getPlayer(opponentUuid); - if (opponent != null) { - opponent.loseLife(loseLife, game, source, false); - } - } - return true; } + return true; + } - @Override - public BanefulOmenEffect copy() { - return new BanefulOmenEffect(this); - } + @Override + public BanefulOmenEffect copy() { + return new BanefulOmenEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/b/BankruptInBlood.java b/Mage.Sets/src/mage/cards/b/BankruptInBlood.java index d5031b9a277..c73be4747ff 100644 --- a/Mage.Sets/src/mage/cards/b/BankruptInBlood.java +++ b/Mage.Sets/src/mage/cards/b/BankruptInBlood.java @@ -5,10 +5,7 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; -import mage.target.common.TargetControlledPermanent; -import mage.target.common.TargetSacrifice; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,14 +14,11 @@ import java.util.UUID; */ public final class BankruptInBlood extends CardImpl { - private static final FilterControlledPermanent filter - = new FilterControlledCreaturePermanent("creatures"); - public BankruptInBlood(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); // As an additional cost to cast this spell, sacrifice two creatures. - this.getSpellAbility().addCost(new SacrificeTargetCost(2, filter)); + this.getSpellAbility().addCost(new SacrificeTargetCost(2, StaticFilters.FILTER_PERMANENT_CREATURES)); // Draw three cards. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3)); diff --git a/Mage.Sets/src/mage/cards/b/BarbedShocker.java b/Mage.Sets/src/mage/cards/b/BarbedShocker.java index c921f095ef5..0bcbb7d2a5a 100644 --- a/Mage.Sets/src/mage/cards/b/BarbedShocker.java +++ b/Mage.Sets/src/mage/cards/b/BarbedShocker.java @@ -66,7 +66,7 @@ class BarbedShockerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer == null || targetPlayer.getHand().isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/b/BarbedWire.java b/Mage.Sets/src/mage/cards/b/BarbedWire.java index 844df035f61..d5b838e92b2 100644 --- a/Mage.Sets/src/mage/cards/b/BarbedWire.java +++ b/Mage.Sets/src/mage/cards/b/BarbedWire.java @@ -71,7 +71,7 @@ class BarbwireDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player activePlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player activePlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (activePlayer != null) { activePlayer.damage(1, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/b/BarterInBlood.java b/Mage.Sets/src/mage/cards/b/BarterInBlood.java index 648986e563e..5fb47a98c54 100644 --- a/Mage.Sets/src/mage/cards/b/BarterInBlood.java +++ b/Mage.Sets/src/mage/cards/b/BarterInBlood.java @@ -4,8 +4,7 @@ import mage.abilities.effects.common.SacrificeAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -14,13 +13,11 @@ import java.util.UUID; */ public final class BarterInBlood extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("creatures"); - public BarterInBlood(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}{B}"); // Each player sacrifices two creatures. - this.getSpellAbility().addEffect(new SacrificeAllEffect(2, filter)); + this.getSpellAbility().addEffect(new SacrificeAllEffect(2, StaticFilters.FILTER_PERMANENT_CREATURES)); } private BarterInBlood(final BarterInBlood card) { diff --git a/Mage.Sets/src/mage/cards/b/BasaltGolem.java b/Mage.Sets/src/mage/cards/b/BasaltGolem.java index ece46148a57..f13c6b69fe7 100644 --- a/Mage.Sets/src/mage/cards/b/BasaltGolem.java +++ b/Mage.Sets/src/mage/cards/b/BasaltGolem.java @@ -72,7 +72,7 @@ class BasaltGolemEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature == null) return false; diff --git a/Mage.Sets/src/mage/cards/b/BattletideAlchemist.java b/Mage.Sets/src/mage/cards/b/BattletideAlchemist.java index cb87497f095..301bd12b158 100644 --- a/Mage.Sets/src/mage/cards/b/BattletideAlchemist.java +++ b/Mage.Sets/src/mage/cards/b/BattletideAlchemist.java @@ -8,11 +8,10 @@ import mage.abilities.effects.PreventionEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.events.DamageEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.events.PreventDamageEvent; import mage.game.events.PreventedDamageEvent; import mage.players.Player; @@ -66,7 +65,7 @@ class BattletideAlchemistEffect extends PreventionEffectImpl { Player controller = game.getPlayer(source.getControllerId()); Player targetPlayer = game.getPlayer(event.getTargetId()); if (controller != null && targetPlayer != null) { - int numberOfClericsControlled = new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent(SubType.CLERIC, "Clerics")).calculate(game, source, this); + int numberOfClericsControlled = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.CLERIC, "Clerics")).calculate(game, source, this); int toPrevent = Math.min(numberOfClericsControlled, event.getAmount()); if (toPrevent > 0 && controller.chooseUse(Outcome.PreventDamage, "Prevent " + toPrevent + " damage to " + targetPlayer.getName() + '?', source, game)) { GameEvent preventEvent = new PreventDamageEvent(event.getTargetId(), source.getSourceId(), source, source.getControllerId(), toPrevent, ((DamageEvent) event).isCombatDamage()); diff --git a/Mage.Sets/src/mage/cards/b/BazaarOfWonders.java b/Mage.Sets/src/mage/cards/b/BazaarOfWonders.java index 301be0507db..21b4231ef81 100644 --- a/Mage.Sets/src/mage/cards/b/BazaarOfWonders.java +++ b/Mage.Sets/src/mage/cards/b/BazaarOfWonders.java @@ -70,7 +70,7 @@ class BazaarOfWondersEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/b/BazaarTrader.java b/Mage.Sets/src/mage/cards/b/BazaarTrader.java index 46c8358f5cc..e74f8049d9a 100644 --- a/Mage.Sets/src/mage/cards/b/BazaarTrader.java +++ b/Mage.Sets/src/mage/cards/b/BazaarTrader.java @@ -1,24 +1,22 @@ - package mage.cards.b; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.TargetPlayerGainControlTargetPermanentEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** * * @author North @@ -42,7 +40,7 @@ public final class BazaarTrader extends CardImpl { this.toughness = new MageInt(1); // {tap}: Target player gains control of target artifact, creature, or land you control. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BazaarTraderEffect(), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TargetPlayerGainControlTargetPermanentEffect(), new TapSourceCost()); ability.addTarget(new TargetPlayer()); ability.addTarget(new TargetControlledPermanent(filter)); this.addAbility(ability); @@ -57,41 +55,3 @@ public final class BazaarTrader extends CardImpl { return new BazaarTrader(this); } } - -class BazaarTraderEffect extends ContinuousEffectImpl { - - MageObjectReference targetPermanentReference; - - public BazaarTraderEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - this.staticText = "Target player gains control of target artifact, creature, or land you control"; - } - - private BazaarTraderEffect(final BazaarTraderEffect effect) { - super(effect); - this.targetPermanentReference = effect.targetPermanentReference; - } - - @Override - public BazaarTraderEffect copy() { - return new BazaarTraderEffect(this); - } - - @Override - public void init(Ability source, Game game) { - super.init(source, game); - targetPermanentReference = new MageObjectReference(source.getTargets().get(1).getFirstTarget(), game); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getFirstTarget()); - Permanent permanent = targetPermanentReference.getPermanent(game); - if (player != null && permanent != null) { - return permanent.changeControllerId(player.getId(), game, source); - } else { - discard(); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BeaconOfDestiny.java b/Mage.Sets/src/mage/cards/b/BeaconOfDestiny.java index 3e4feefb4ee..36c5a8cf87e 100644 --- a/Mage.Sets/src/mage/cards/b/BeaconOfDestiny.java +++ b/Mage.Sets/src/mage/cards/b/BeaconOfDestiny.java @@ -71,8 +71,8 @@ class BeaconOfDestinyEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { - this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BehindTheMask.java b/Mage.Sets/src/mage/cards/b/BehindTheMask.java new file mode 100644 index 00000000000..bcfb581e219 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BehindTheMask.java @@ -0,0 +1,53 @@ +package mage.cards.b; + +import mage.abilities.condition.common.CollectedEvidenceCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.keyword.CollectEvidenceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class BehindTheMask extends CardImpl { + + public BehindTheMask(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // As an additional cost to cast this spell, you may collect evidence 6. + this.addAbility(new CollectEvidenceAbility(6)); + + // Until end of turn, target artifact or creature becomes an artifact creature with base power and toughness 4/3. + // If evidence was collected, it has base power and toughness 1/1 until end of turn instead. + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + this.getSpellAbility().addEffect(new ConditionalContinuousEffect( + new BecomesCreatureTargetEffect(new CreatureToken(1, 1).withType(CardType.ARTIFACT), + false, false, Duration.EndOfTurn + ), + new BecomesCreatureTargetEffect(new CreatureToken(4, 3).withType(CardType.ARTIFACT), + false, false, Duration.EndOfTurn + ), + CollectedEvidenceCondition.instance, + "Until end of turn, target artifact or creature becomes an artifact creature with base power and toughness 4/3. " + + "If evidence was collected, it has base power and toughness 1/1 until end of turn instead." + )); + + } + + private BehindTheMask(final BehindTheMask card) { + super(card); + } + + @Override + public BehindTheMask copy() { + return new BehindTheMask(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BelakorTheDarkMaster.java b/Mage.Sets/src/mage/cards/b/BelakorTheDarkMaster.java index de3c5d8f79d..2ebd76ffe69 100644 --- a/Mage.Sets/src/mage/cards/b/BelakorTheDarkMaster.java +++ b/Mage.Sets/src/mage/cards/b/BelakorTheDarkMaster.java @@ -78,7 +78,7 @@ public final class BelakorTheDarkMaster extends CardImpl { class BelakorTheDarkMasterEffect extends OneShotEffect { BelakorTheDarkMasterEffect() { - super(Outcome.Benefit); + super(Outcome.Damage); staticText = "it deals damage equal to its power to any target"; } diff --git a/Mage.Sets/src/mage/cards/b/Besmirch.java b/Mage.Sets/src/mage/cards/b/Besmirch.java index aa377139f52..83f68a4e623 100644 --- a/Mage.Sets/src/mage/cards/b/Besmirch.java +++ b/Mage.Sets/src/mage/cards/b/Besmirch.java @@ -61,22 +61,22 @@ class BesmirchEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { if (game.getPermanent(source.getFirstTarget()) != null) { - TargetPointer target = new FixedTarget(source.getFirstTarget(), game); + TargetPointer blueprintTarget = new FixedTarget(source.getFirstTarget(), game); // gain control game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn) - .setTargetPointer(target), source); + .setTargetPointer(blueprintTarget.copy()), source); // haste game.addEffect(new GainAbilityTargetEffect( HasteAbility.getInstance(), Duration.EndOfTurn - ).setTargetPointer(target), source); + ).setTargetPointer(blueprintTarget.copy()), source); // goad - game.addEffect(new GoadTargetEffect().setTargetPointer(target), source); + game.addEffect(new GoadTargetEffect().setTargetPointer(blueprintTarget.copy()), source); // untap - new UntapTargetEffect().setTargetPointer(target).apply(game, source); + new UntapTargetEffect().setTargetPointer(blueprintTarget.copy()).apply(game, source); return true; } diff --git a/Mage.Sets/src/mage/cards/b/BessSoulNourisher.java b/Mage.Sets/src/mage/cards/b/BessSoulNourisher.java index de0768d98c4..ac9db099dbf 100644 --- a/Mage.Sets/src/mage/cards/b/BessSoulNourisher.java +++ b/Mage.Sets/src/mage/cards/b/BessSoulNourisher.java @@ -53,7 +53,7 @@ public class BessSoulNourisher extends CardImpl { DynamicValue xValue = new CountersSourceCount(CounterType.P1P1); this.addAbility(new AttacksTriggeredAbility(new BoostControlledEffect( xValue, xValue, Duration.EndOfTurn, - StaticFilters.FILTER_PERMANENT_CREATURE, true + filter, true ).setText("each other creature you control with base power and toughness 1/1 " + "gets +X/+X until end of turn, where X is the number of +1/+1 counters on {this}"), false)); diff --git a/Mage.Sets/src/mage/cards/b/BillFernyBreeSwindler.java b/Mage.Sets/src/mage/cards/b/BillFernyBreeSwindler.java index 313560e5825..873621fd2fe 100644 --- a/Mage.Sets/src/mage/cards/b/BillFernyBreeSwindler.java +++ b/Mage.Sets/src/mage/cards/b/BillFernyBreeSwindler.java @@ -68,7 +68,7 @@ class BillFernyEffect extends OneShotEffect { private static final Effect create3TreasureTokens = new CreateTokenEffect(new TreasureToken(), 3); private static final Effect removeFromCombat = new RemoveFromCombatSourceEffect(); - public BillFernyEffect() { + BillFernyEffect() { super(Outcome.Benefit); this.staticText = "Target opponent gains control of target Horse you control. If they do, remove Bill Ferny from combat and create three Treasure tokens."; } @@ -88,7 +88,7 @@ class BillFernyEffect extends OneShotEffect { if (permanent == null) { return false; } - UUID opponentToGainControl = targetPointer.getFirst(game, source); + UUID opponentToGainControl = getTargetPointer().getFirst(game, source); game.addEffect(new GainControlTargetEffect( Duration.Custom, true, opponentToGainControl ).setTargetPointer(new FixedTarget(permanent.getId(), game)), source); diff --git a/Mage.Sets/src/mage/cards/b/BiolumeEgg.java b/Mage.Sets/src/mage/cards/b/BiolumeEgg.java index bc49e8359fd..9f5b8140020 100644 --- a/Mage.Sets/src/mage/cards/b/BiolumeEgg.java +++ b/Mage.Sets/src/mage/cards/b/BiolumeEgg.java @@ -81,7 +81,7 @@ class BiolumeEggEffect extends OneShotEffect { if (controller == null) { return false; } - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), Boolean.TRUE); controller.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null); diff --git a/Mage.Sets/src/mage/cards/b/BiomancersFamiliar.java b/Mage.Sets/src/mage/cards/b/BiomancersFamiliar.java index ec6e5e04d5e..48d44e5cf51 100644 --- a/Mage.Sets/src/mage/cards/b/BiomancersFamiliar.java +++ b/Mage.Sets/src/mage/cards/b/BiomancersFamiliar.java @@ -126,7 +126,7 @@ class BiomancersFamiliarReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(targetPointer.getFirst(game, source)); + return event.getTargetId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BlanketOfNight.java b/Mage.Sets/src/mage/cards/b/BlanketOfNight.java index 6b071f0b61c..a2a7254a22b 100644 --- a/Mage.Sets/src/mage/cards/b/BlanketOfNight.java +++ b/Mage.Sets/src/mage/cards/b/BlanketOfNight.java @@ -1,16 +1,11 @@ - package mage.cards.b; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; -import mage.abilities.mana.BlackManaAbility; +import mage.abilities.effects.common.continuous.AddBasicLandTypeAllLandsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.StaticFilters; -import mage.filter.common.FilterLandPermanent; +import mage.constants.CardType; +import mage.constants.SubType; import java.util.UUID; @@ -22,13 +17,9 @@ public final class BlanketOfNight extends CardImpl { public BlanketOfNight(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}"); - // Each land is a Swamp in addition to its other land types. - Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAllEffect(new BlackManaAbility(), Duration.WhileOnBattlefield, new FilterLandPermanent(), - "Each land is a Swamp in addition to its other land types")); - ability.addEffect(new AddCardSubtypeAllEffect(StaticFilters.FILTER_LAND, SubType.SWAMP, DependencyType.BecomeSwamp)); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new AddBasicLandTypeAllLandsEffect(SubType.SWAMP))); } private BlanketOfNight(final BlanketOfNight card) { diff --git a/Mage.Sets/src/mage/cards/b/BlazeOfGlory.java b/Mage.Sets/src/mage/cards/b/BlazeOfGlory.java index d28d802762e..b77aee8c7f5 100644 --- a/Mage.Sets/src/mage/cards/b/BlazeOfGlory.java +++ b/Mage.Sets/src/mage/cards/b/BlazeOfGlory.java @@ -65,7 +65,7 @@ class BlazeOfGloryRequirementEffect extends RequirementEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getId().equals(targetPointer.getFirst(game, source)); + return permanent.getId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BlazingSalvo.java b/Mage.Sets/src/mage/cards/b/BlazingSalvo.java index 818d556341c..9e0fa2c53cd 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingSalvo.java +++ b/Mage.Sets/src/mage/cards/b/BlazingSalvo.java @@ -54,7 +54,7 @@ class BlazingSalvoEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { Player player = game.getPlayer(permanent.getControllerId()); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/b/BlightwingBandit.java b/Mage.Sets/src/mage/cards/b/BlightwingBandit.java index 2a50140e244..dde41a1de56 100644 --- a/Mage.Sets/src/mage/cards/b/BlightwingBandit.java +++ b/Mage.Sets/src/mage/cards/b/BlightwingBandit.java @@ -80,7 +80,7 @@ class BlightwingBanditEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (controller == null || opponent == null || sourceObject == null) { diff --git a/Mage.Sets/src/mage/cards/b/BlimComedicGenius.java b/Mage.Sets/src/mage/cards/b/BlimComedicGenius.java index 8d92041d0e9..36e5a26313a 100644 --- a/Mage.Sets/src/mage/cards/b/BlimComedicGenius.java +++ b/Mage.Sets/src/mage/cards/b/BlimComedicGenius.java @@ -84,7 +84,7 @@ class BlimComedicGeniusEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { game.addEffect(new GainControlTargetEffect( - Duration.Custom, true, targetPointer.getFirst(game, source) + Duration.Custom, true, getTargetPointer().getFirst(game, source) ).setTargetPointer(new FixedTarget(source.getFirstTarget(), game)), source); game.getState().processAction(game); Map cardsMap = new HashMap<>(); diff --git a/Mage.Sets/src/mage/cards/b/BlindingRadiance.java b/Mage.Sets/src/mage/cards/b/BlindingRadiance.java deleted file mode 100644 index 773ee1b112c..00000000000 --- a/Mage.Sets/src/mage/cards/b/BlindingRadiance.java +++ /dev/null @@ -1,40 +0,0 @@ -package mage.cards.b; - -import mage.abilities.effects.common.TapAllEffect; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.common.FilterOpponentsCreaturePermanent; -import mage.filter.predicate.mageobject.ToughnessPredicate; - -import java.util.UUID; - -/** - * @author JayDi85 - */ -public final class BlindingRadiance extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterOpponentsCreaturePermanent("creatures your opponents control with toughness 2 or less"); - static { - filter.add(new ToughnessPredicate(ComparisonType.FEWER_THAN, 3)); - } - - public BlindingRadiance(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}"); - - // Tap all creatures your opponents control with toughness 2 or less. - TapAllEffect effect = new TapAllEffect(filter); - this.getSpellAbility().addEffect(effect); - } - - private BlindingRadiance(final BlindingRadiance card) { - super(card); - } - - @Override - public BlindingRadiance copy() { - return new BlindingRadiance(this); - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BlinkmothUrn.java b/Mage.Sets/src/mage/cards/b/BlinkmothUrn.java index 7f1b9233581..b0309f72c06 100644 --- a/Mage.Sets/src/mage/cards/b/BlinkmothUrn.java +++ b/Mage.Sets/src/mage/cards/b/BlinkmothUrn.java @@ -4,6 +4,8 @@ import java.util.UUID; import mage.Mana; import mage.abilities.Ability; import mage.abilities.common.BeginningOfPreCombatMainTriggeredAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -25,10 +27,11 @@ public final class BlinkmothUrn extends CardImpl { public BlinkmothUrn(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); - // At the beginning of each player's precombat main phase, if - // Blinkmoth Urn is untapped, that player adds {1} to their - // mana pool for each artifact they control. - this.addAbility(new BeginningOfPreCombatMainTriggeredAbility(new BlinkmothUrnEffect(), TargetController.ANY, false)); + // At the beginning of each player's precombat main phase, if Blinkmoth Urn is untapped, that player adds {C} for each artifact they control. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfPreCombatMainTriggeredAbility(new BlinkmothUrnEffect(), TargetController.ANY, false), SourceTappedCondition.UNTAPPED, + "At the beginning of each player's precombat main phase, if {this} is untapped, that player adds {C} for each artifact they control." + )); } private BlinkmothUrn(final BlinkmothUrn card) { @@ -46,7 +49,7 @@ class BlinkmothUrnEffect extends OneShotEffect { BlinkmothUrnEffect() { super(Outcome.PutManaInPool); - this.staticText = "if Blinkmoth Urn is untapped, that player adds {C} for each artifact they control"; + this.staticText = "that player adds {C} for each artifact they control"; } private BlinkmothUrnEffect(final BlinkmothUrnEffect effect) { @@ -64,7 +67,7 @@ class BlinkmothUrnEffect extends OneShotEffect { FilterArtifactPermanent filter = new FilterArtifactPermanent("artifacts you control"); filter.add(new ControllerIdPredicate(game.getActivePlayerId())); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (player != null && sourcePermanent != null && !sourcePermanent.isTapped()) { + if (player != null && sourcePermanent != null) { player.getManaPool().addMana(Mana.ColorlessMana( game.getState().getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game). size()), game, source, false); diff --git a/Mage.Sets/src/mage/cards/b/BlizzardSpecter.java b/Mage.Sets/src/mage/cards/b/BlizzardSpecter.java index 2432953707d..75a517f76d2 100644 --- a/Mage.Sets/src/mage/cards/b/BlizzardSpecter.java +++ b/Mage.Sets/src/mage/cards/b/BlizzardSpecter.java @@ -74,7 +74,7 @@ class ReturnToHandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/b/BloodClock.java b/Mage.Sets/src/mage/cards/b/BloodClock.java index 9cf15efdd0a..1389a74f50e 100644 --- a/Mage.Sets/src/mage/cards/b/BloodClock.java +++ b/Mage.Sets/src/mage/cards/b/BloodClock.java @@ -60,7 +60,7 @@ class BloodClockEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/b/BloodHound.java b/Mage.Sets/src/mage/cards/b/BloodHound.java index f7fb93c540e..c7cfffaea0a 100644 --- a/Mage.Sets/src/mage/cards/b/BloodHound.java +++ b/Mage.Sets/src/mage/cards/b/BloodHound.java @@ -1,4 +1,3 @@ - package mage.cards.b; import mage.MageInt; @@ -35,7 +34,9 @@ public final class BloodHound extends CardImpl { // At the beginning of your end step, remove all +1/+1 counters from Blood Hound. this.addAbility(new BeginningOfEndStepTriggeredAbility( - new RemoveAllCountersSourceEffect(CounterType.P1P1), TargetController.YOU, false + new RemoveAllCountersSourceEffect(CounterType.P1P1) + .setText("remove all +1/+1 counters from {this}"), + TargetController.YOU, false )); } diff --git a/Mage.Sets/src/mage/cards/b/BloodOperative.java b/Mage.Sets/src/mage/cards/b/BloodOperative.java index 9f8ec0cc94a..215ac526d59 100644 --- a/Mage.Sets/src/mage/cards/b/BloodOperative.java +++ b/Mage.Sets/src/mage/cards/b/BloodOperative.java @@ -3,8 +3,8 @@ package mage.cards.b; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SurveilTriggeredAbility; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.ExileTargetEffect; @@ -15,9 +15,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.players.Player; import mage.target.common.TargetCardInGraveyard; /** @@ -43,7 +40,9 @@ public final class BloodOperative extends CardImpl { this.addAbility(ability); // Whenever you surveil, if Blood Operative is in your graveyard, you may pay 3 life. If you do, return Blood Operative to your hand. - this.addAbility(new BloodOperativeTriggeredAbility()); + this.addAbility(new SurveilTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid( + new ReturnSourceFromGraveyardToHandEffect().setText("return {this} to your hand"), new PayLifeCost(3) + ))); } private BloodOperative(final BloodOperative card) { @@ -55,43 +54,3 @@ public final class BloodOperative extends CardImpl { return new BloodOperative(this); } } - -class BloodOperativeTriggeredAbility extends TriggeredAbilityImpl { - - public BloodOperativeTriggeredAbility() { - super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnSourceFromGraveyardToHandEffect(), new PayLifeCost(3)), false); - } - - private BloodOperativeTriggeredAbility(final BloodOperativeTriggeredAbility ability) { - super(ability); - } - - @Override - public BloodOperativeTriggeredAbility copy() { - return new BloodOperativeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SURVEILED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(getControllerId()); - } - - @Override - public boolean checkInterveningIfClause(Game game) { - Player controller = game.getPlayer(getControllerId()); - if (controller != null && controller.getGraveyard().contains(getSourceId())) { - return super.checkInterveningIfClause(game); - } - return false; - } - - @Override - public String getRule() { - return "Whenever you surveil, if {this} is in your graveyard, you may pay 3 life. If you do, return {this} to your hand."; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java new file mode 100644 index 00000000000..764b63a052d --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java @@ -0,0 +1,105 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class BloodSpatterAnalysis extends CardImpl { + + public BloodSpatterAnalysis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{R}"); + + // When Blood Spatter Analysis enters the battlefield, it deals 3 damage to target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // Whenever one or more creatures die, mill a card and put a bloodstain counter on Blood Spatter Analysis. + // Then sacrifice it if it has five or more bloodstain counters on it. + // When you do, return target creature card from your graveyard to your hand. + this.addAbility(new BloodSpatterAnalysisTriggeredAbility()); + } + + private BloodSpatterAnalysis(final BloodSpatterAnalysis card) { + super(card); + } + + @Override + public BloodSpatterAnalysis copy() { + return new BloodSpatterAnalysis(this); + } +} + +class BloodSpatterAnalysisTriggeredAbility extends TriggeredAbilityImpl { + + BloodSpatterAnalysisTriggeredAbility() { + super(Zone.BATTLEFIELD, new MillCardsControllerEffect(1)); + this.addEffect(new AddCountersSourceEffect(CounterType.BLOODSTAIN.createInstance()).concatBy("and")); + + ReflexiveTriggeredAbility returnAbility = new ReflexiveTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect(), false); + returnAbility.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + + this.addEffect(new ConditionalOneShotEffect( + new DoWhenCostPaid(returnAbility, new SacrificeSourceCost(), null, false), + new SourceHasCounterCondition(CounterType.BLOODSTAIN, 5)) + .setText("Then sacrifice it if it has five or more bloodstain counters on it. When you do, return target creature card from your graveyard to your hand")); + + setTriggerPhrase("Whenever one or more creatures die, "); + } + + private BloodSpatterAnalysisTriggeredAbility(final BloodSpatterAnalysisTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeBatchEvent zBatchEvent = (ZoneChangeBatchEvent) event; + + for (ZoneChangeEvent zEvent : zBatchEvent.getEvents()) { + if (zEvent.isDiesEvent()) { + Permanent permanent = game.getPermanentOrLKIBattlefield(zEvent.getTargetId()); + if (permanent != null && permanent.isCreature(game)) { + return true; + } + } + } + return false; + } + + @Override + public BloodSpatterAnalysisTriggeredAbility copy() { + return new BloodSpatterAnalysisTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BloodbondMarch.java b/Mage.Sets/src/mage/cards/b/BloodbondMarch.java index f01452358cf..1acfeca7b43 100644 --- a/Mage.Sets/src/mage/cards/b/BloodbondMarch.java +++ b/Mage.Sets/src/mage/cards/b/BloodbondMarch.java @@ -38,47 +38,48 @@ public final class BloodbondMarch extends CardImpl { return new BloodbondMarch(this); } - private class BloodbondMarchEffect extends OneShotEffect { +} - BloodbondMarchEffect() { - super(Outcome.Benefit); - staticText = "each player returns all cards with the same name as that spell from their graveyard to the battlefield"; +class BloodbondMarchEffect extends OneShotEffect { + + BloodbondMarchEffect() { + super(Outcome.Benefit); + staticText = "each player returns all cards with the same name as that spell from their graveyard to the battlefield"; + } + + private BloodbondMarchEffect(final BloodbondMarchEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + if (controller == null || game.getPermanentOrLKIBattlefield(source.getSourceId()) == null) { + return false; } - private BloodbondMarchEffect(final BloodbondMarchEffect effect) { - super(effect); + Spell spell = game.getSpellOrLKIStack(this.getTargetPointer().getFirst(game, source)); + + if (spell == null) { + return false; } - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); + FilterCard filter = new FilterCard(); + filter.add(new NamePredicate(spell.getName())); - if (controller == null || game.getPermanentOrLKIBattlefield(source.getSourceId()) == null) { - return false; + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.moveCards(player.getGraveyard().getCards(filter, game), Zone.BATTLEFIELD, source, game); } - - Spell spell = game.getSpellOrLKIStack(this.getTargetPointer().getFirst(game, source)); - - if (spell == null) { - return false; - } - - FilterCard filter = new FilterCard(); - filter.add(new NamePredicate(spell.getName())); - - for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { - Player player = game.getPlayer(playerId); - if (player != null) { - player.moveCards(player.getGraveyard().getCards(filter, game), Zone.BATTLEFIELD, source, game); - } - } - - return true; } - @Override - public BloodbondMarchEffect copy() { - return new BloodbondMarchEffect(this); - } + return true; + } + + @Override + public BloodbondMarchEffect copy() { + return new BloodbondMarchEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/b/BloodlineShaman.java b/Mage.Sets/src/mage/cards/b/BloodlineShaman.java index 3221fcb6e52..d50f28a940d 100644 --- a/Mage.Sets/src/mage/cards/b/BloodlineShaman.java +++ b/Mage.Sets/src/mage/cards/b/BloodlineShaman.java @@ -1,4 +1,3 @@ - package mage.cards.b; import java.util.UUID; @@ -15,7 +14,6 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; @@ -80,10 +78,8 @@ class BloodlineShamanEffect extends OneShotEffect { if (!controller.choose(outcome, typeChoice, game)) { return false; } - + SubType subType = SubType.byDescription(typeChoice.getChoice()); game.informPlayers(sourceObject.getLogName() + " chosen type: " + typeChoice.getChoice()); - FilterCard filterSubtype = new FilterCard(); - filterSubtype.add(SubType.byDescription(typeChoice.getChoice()).getPredicate()); // Reveal the top card of your library. if (controller.getLibrary().hasCards()) { @@ -93,7 +89,7 @@ class BloodlineShamanEffect extends OneShotEffect { if (card != null) { // If that card is a creature card of the chosen type, put it into your hand. - if (filterSubtype.match(card, game)) { + if (card.isCreature(game) && subType != null && card.getSubtype(game).contains(subType)) { controller.moveCards(card, Zone.HAND, source, game); // Otherwise, put it into your graveyard. } else { diff --git a/Mage.Sets/src/mage/cards/b/BloodlordOfVaasgoth.java b/Mage.Sets/src/mage/cards/b/BloodlordOfVaasgoth.java index ee4a0001242..b545e6e2658 100644 --- a/Mage.Sets/src/mage/cards/b/BloodlordOfVaasgoth.java +++ b/Mage.Sets/src/mage/cards/b/BloodlordOfVaasgoth.java @@ -82,7 +82,7 @@ class BloodlordOfVaasgothEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); - Spell object = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell object = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (object != null) { zoneChangeCounter = game.getState().getZoneChangeCounter(object.getSourceId()) + 1; permanentId = object.getSourceId(); @@ -98,7 +98,7 @@ class BloodlordOfVaasgothEffect extends ContinuousEffectImpl { if (game.getState().getZoneChangeCounter(permanentId) >= zoneChangeCounter) { discard(); } - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { // Bloodthirst checked while spell is on the stack so needed to give it already to the spell game.getState().addOtherAbility(spell.getCard(), ability, true); } diff --git a/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java b/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java index f8b2fcc7adb..6aa6b4d61f4 100644 --- a/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java +++ b/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java @@ -124,7 +124,7 @@ class BloodthirstyAdversaryCopyEffect extends OneShotEffect { return false; } Set cardsToExile = new LinkedHashSet<>(); - for (UUID cardId : targetPointer.getTargets(game, source)) { + for (UUID cardId : getTargetPointer().getTargets(game, source)) { Card card = game.getCard(cardId); if (card != null) { cardsToExile.add(card); diff --git a/Mage.Sets/src/mage/cards/b/BomatCourier.java b/Mage.Sets/src/mage/cards/b/BomatCourier.java index 4ae9d51a81e..ccf0f49a78b 100644 --- a/Mage.Sets/src/mage/cards/b/BomatCourier.java +++ b/Mage.Sets/src/mage/cards/b/BomatCourier.java @@ -3,23 +3,18 @@ package mage.cards.b; import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.DiscardHandCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ColoredManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; import mage.abilities.keyword.HasteAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.ExileZone; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; /** * @@ -37,10 +32,15 @@ public final class BomatCourier extends CardImpl { this.addAbility(HasteAbility.getInstance()); // Whenever Bomat Courier attacks, exile the top card of your library face down. - this.addAbility(new AttacksTriggeredAbility(new BomatCourierExileEffect(), false)); + this.addAbility(new AttacksTriggeredAbility( + new ExileCardsFromTopOfLibraryControllerEffect(1, true, true, true), + false)); // {R}, Discard your hand, Sacrifice Bomat Courier: Put all cards exiled with Bomat Courier into their owners' hands. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BomatCourierReturnEffect(), new ColoredManaCost(ColoredManaSymbol.R)); + Ability ability = new SimpleActivatedAbility( + Zone.BATTLEFIELD, + new ReturnFromExileForSourceEffect(Zone.HAND).withText(true, true, true), + new ColoredManaCost(ColoredManaSymbol.R)); ability.addCost(new DiscardHandCost()); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); @@ -55,67 +55,3 @@ public final class BomatCourier extends CardImpl { return new BomatCourier(this); } } - -class BomatCourierExileEffect extends OneShotEffect { - - BomatCourierExileEffect() { - super(Outcome.Exile); - this.staticText = "exile the top card of your library face down"; - } - - private BomatCourierExileEffect(final BomatCourierExileEffect effect) { - super(effect); - } - - @Override - public BomatCourierExileEffect copy() { - return new BomatCourierExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName()); - card.setFaceDown(true, game); - return true; - } - } - return false; - } -} - -class BomatCourierReturnEffect extends OneShotEffect { - - BomatCourierReturnEffect() { - super(Outcome.DrawCard); - this.staticText = "Put all cards exiled with {this} into their owners' hands"; - } - - private BomatCourierReturnEffect(final BomatCourierReturnEffect effect) { - super(effect); - } - - @Override - public BomatCourierReturnEffect copy() { - return new BomatCourierReturnEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); - if (exileZone != null) { - controller.moveCards(exileZone, Zone.HAND, source, game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BoneDancer.java b/Mage.Sets/src/mage/cards/b/BoneDancer.java index b19628d7aac..2b4b62846f5 100644 --- a/Mage.Sets/src/mage/cards/b/BoneDancer.java +++ b/Mage.Sets/src/mage/cards/b/BoneDancer.java @@ -61,7 +61,7 @@ class BoneDancerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player defendingPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player defendingPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && defendingPlayer != null) { Card lastCreatureCard = null; for (Card card : defendingPlayer.getGraveyard().getCards(game)) { diff --git a/Mage.Sets/src/mage/cards/b/BoneMask.java b/Mage.Sets/src/mage/cards/b/BoneMask.java index 6353ac9ac9d..574a6c78eaf 100644 --- a/Mage.Sets/src/mage/cards/b/BoneMask.java +++ b/Mage.Sets/src/mage/cards/b/BoneMask.java @@ -68,8 +68,8 @@ class BoneMaskEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BonecladNecromancer.java b/Mage.Sets/src/mage/cards/b/BonecladNecromancer.java index ef604348192..8159a154c49 100644 --- a/Mage.Sets/src/mage/cards/b/BonecladNecromancer.java +++ b/Mage.Sets/src/mage/cards/b/BonecladNecromancer.java @@ -9,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.game.permanent.token.ZombieToken; import mage.target.common.TargetCardInGraveyard; @@ -20,8 +20,6 @@ import java.util.UUID; */ public final class BonecladNecromancer extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("creature card from a graveyard"); - public BonecladNecromancer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); @@ -32,7 +30,7 @@ public final class BonecladNecromancer extends CardImpl { // When Boneclad Necromancer enters the battlefield, you may exile target creature card from a graveyard. If you do, create a 2/2 black Zombie creature token. Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect(), true); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); ability.addEffect(new CreateTokenEffect(new ZombieToken()).concatBy("If you do,")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BoomerScrapper.java b/Mage.Sets/src/mage/cards/b/BoomerScrapper.java new file mode 100644 index 00000000000..c9984b290ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoomerScrapper.java @@ -0,0 +1,60 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +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.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.JunkToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BoomerScrapper extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("a token you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public BoomerScrapper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever Boomer Scrapper enters the battlefield or attacks, you lose 1 life and create a Junk token. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new LoseLifeSourceControllerEffect(1)); + ability.addEffect(new CreateTokenEffect(new JunkToken()).concatBy("and")); + this.addAbility(ability); + + // Whenever a token you control leaves the battlefield, put a +1/+1 counter on Boomer Scrapper. + this.addAbility(new LeavesBattlefieldAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter, false + )); + } + + private BoomerScrapper(final BoomerScrapper card) { + super(card); + } + + @Override + public BoomerScrapper copy() { + return new BoomerScrapper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/Borborygmos.java b/Mage.Sets/src/mage/cards/b/Borborygmos.java index 38e25e6fb4b..e84186eb6e7 100644 --- a/Mage.Sets/src/mage/cards/b/Borborygmos.java +++ b/Mage.Sets/src/mage/cards/b/Borborygmos.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -31,7 +31,7 @@ public final class Borborygmos extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever Borborygmos deals combat damage to a player, put a +1/+1 counter on each creature you control. - this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()), false)); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE), false)); } private Borborygmos(final Borborygmos card) { diff --git a/Mage.Sets/src/mage/cards/b/BoundDetermined.java b/Mage.Sets/src/mage/cards/b/BoundDetermined.java index 605d24d4026..1c183db8c44 100644 --- a/Mage.Sets/src/mage/cards/b/BoundDetermined.java +++ b/Mage.Sets/src/mage/cards/b/BoundDetermined.java @@ -18,14 +18,14 @@ import mage.constants.Outcome; import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetSacrifice; /** * @@ -79,27 +79,27 @@ class BoundEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - TargetControlledPermanent target = new TargetControlledPermanent(1, 1, new FilterControlledCreaturePermanent("a creature (to sacrifice)"), true); - if (target.canChoose(controller.getId(), source, game)) { - if (controller.chooseTarget(outcome, target, source, game)) { - Permanent toSacrifice = game.getPermanent(target.getFirstTarget()); - if (toSacrifice != null) { - toSacrifice.sacrifice(source, game); - game.getState().processAction(game); - int colors = toSacrifice.getColor(game).getColorCount(); - if (colors > 0) { - TargetCardInYourGraveyard targetCard = new TargetCardInYourGraveyard(0, colors, - new FilterCard("up to " + colors + " card" + (colors > 1 ? "s" : "") + " from your graveyard")); - controller.chooseTarget(outcome, targetCard, source, game); - controller.moveCards(new CardsImpl(targetCard.getTargets()), Zone.HAND, source, game); - } - } + if (controller == null) { + return false; + } + TargetSacrifice target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); + if (target.canChoose(controller.getId(), source, game) + && (controller.chooseTarget(outcome, target, source, game))) { + Permanent toSacrifice = game.getPermanent(target.getFirstTarget()); + if (toSacrifice != null) { + toSacrifice.sacrifice(source, game); + game.getState().processAction(game); + int colors = toSacrifice.getColor(game).getColorCount(); + if (colors > 0) { + TargetCardInYourGraveyard targetCard = new TargetCardInYourGraveyard(0, colors, + new FilterCard("up to " + colors + " card" + (colors > 1 ? "s" : "") + " from your graveyard")); + controller.chooseTarget(outcome, targetCard, source, game); + controller.moveCards(new CardsImpl(targetCard.getTargets()), Zone.HAND, source, game); } } - return true; + } - return false; + return true; } } diff --git a/Mage.Sets/src/mage/cards/b/BrainPry.java b/Mage.Sets/src/mage/cards/b/BrainPry.java index 8da4705054a..c4eed9a37ca 100644 --- a/Mage.Sets/src/mage/cards/b/BrainPry.java +++ b/Mage.Sets/src/mage/cards/b/BrainPry.java @@ -53,7 +53,7 @@ class BrainPryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); diff --git a/Mage.Sets/src/mage/cards/b/BrambleFamiliar.java b/Mage.Sets/src/mage/cards/b/BrambleFamiliar.java index 7188cdab4ea..0229ac3003a 100644 --- a/Mage.Sets/src/mage/cards/b/BrambleFamiliar.java +++ b/Mage.Sets/src/mage/cards/b/BrambleFamiliar.java @@ -84,8 +84,8 @@ class FetchQuestEffect extends OneShotEffect { FetchQuestEffect() { super(Outcome.Benefit); - staticText = "mill seven cards, then put a creature, enchantment, or land card " - + "from among cards milled this way onto the battlefield"; + staticText = "mill seven cards. Then put a creature, enchantment, or land card " + + "from among the milled cards onto the battlefield"; } private FetchQuestEffect(final FetchQuestEffect effect) { @@ -115,4 +115,4 @@ class FetchQuestEffect extends OneShotEffect { return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BrambleSovereign.java b/Mage.Sets/src/mage/cards/b/BrambleSovereign.java index 82802cf07b6..98a23983694 100644 --- a/Mage.Sets/src/mage/cards/b/BrambleSovereign.java +++ b/Mage.Sets/src/mage/cards/b/BrambleSovereign.java @@ -80,7 +80,7 @@ class BrambleSovereignEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(permanent.getControllerId()); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/b/BreakDown.java b/Mage.Sets/src/mage/cards/b/BreakDown.java new file mode 100644 index 00000000000..92ff1ba95a0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BreakDown.java @@ -0,0 +1,36 @@ +package mage.cards.b; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.JunkToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BreakDown extends CardImpl { + + public BreakDown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); + + // Destroy target artifact or enchantment. Create a Junk token. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new JunkToken())); + } + + private BreakDown(final BreakDown card) { + super(card); + } + + @Override + public BreakDown copy() { + return new BreakDown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BriaRiptideRogue.java b/Mage.Sets/src/mage/cards/b/BriaRiptideRogue.java new file mode 100644 index 00000000000..bb1c85007cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BriaRiptideRogue.java @@ -0,0 +1,60 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BriaRiptideRogue extends CardImpl { + + public BriaRiptideRogue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.OTTER); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Other creatures you control have prowess. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new ProwessAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES, true + ))); + + // Whenever you cast a noncreature spell, target creature you control can't be blocked this turn. + Ability ability = new SpellCastControllerTriggeredAbility( + new CantBeBlockedTargetEffect(), StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private BriaRiptideRogue(final BriaRiptideRogue card) { + super(card); + } + + @Override + public BriaRiptideRogue copy() { + return new BriaRiptideRogue(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/Brightflame.java b/Mage.Sets/src/mage/cards/b/Brightflame.java index 4dfbe521dcf..063f3b13711 100644 --- a/Mage.Sets/src/mage/cards/b/Brightflame.java +++ b/Mage.Sets/src/mage/cards/b/Brightflame.java @@ -66,7 +66,7 @@ class BrightflameEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int damageDealt = 0; - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/b/BringTheEnding.java b/Mage.Sets/src/mage/cards/b/BringTheEnding.java index d8d0d5a28b3..c3780fd728c 100644 --- a/Mage.Sets/src/mage/cards/b/BringTheEnding.java +++ b/Mage.Sets/src/mage/cards/b/BringTheEnding.java @@ -71,10 +71,10 @@ class BringTheEndingCounterEffect extends OneShotEffect { Player player = game.getPlayer(controllerId); if (player != null && player.getCounters().getCount(CounterType.POISON) >= 3) { - hardCounterEffect.setTargetPointer(this.getTargetPointer()); + hardCounterEffect.setTargetPointer(this.getTargetPointer().copy()); return hardCounterEffect.apply(game, source); } else { - softCounterEffect.setTargetPointer(this.getTargetPointer()); + softCounterEffect.setTargetPointer(this.getTargetPointer().copy()); return softCounterEffect.apply(game, source); } diff --git a/Mage.Sets/src/mage/cards/b/BronzeHorse.java b/Mage.Sets/src/mage/cards/b/BronzeHorse.java index 1aa84635414..976f8b84fb7 100644 --- a/Mage.Sets/src/mage/cards/b/BronzeHorse.java +++ b/Mage.Sets/src/mage/cards/b/BronzeHorse.java @@ -59,7 +59,7 @@ class BronzeHorsePreventionEffect extends PreventAllDamageToSourceEffect { } private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); - public BronzeHorsePreventionEffect() { + BronzeHorsePreventionEffect() { super(Duration.WhileOnBattlefield); staticText = "as long as you control another creature, prevent all damage that would be dealt to {this} by spells that target it"; } diff --git a/Mage.Sets/src/mage/cards/b/BrotherhoodOutcast.java b/Mage.Sets/src/mage/cards/b/BrotherhoodOutcast.java new file mode 100644 index 00000000000..71c3772f137 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrotherhoodOutcast.java @@ -0,0 +1,65 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BrotherhoodOutcast extends CardImpl { + + private static final FilterCard filter = new FilterCard("Aura or Equipment card with mana value 3 or less from your graveyard"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public BrotherhoodOutcast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Brotherhood Outcast enters the battlefield, choose one -- + // * Return target Aura or Equipment card with mana value 3 or less from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + + // * Put a shield counter on target creature. + ability.addMode(new Mode(new AddCountersTargetEffect(CounterType.SHIELD.createInstance())) + .addTarget(new TargetCreaturePermanent())); + this.addAbility(ability); + } + + private BrotherhoodOutcast(final BrotherhoodOutcast card) { + super(card); + } + + @Override + public BrotherhoodOutcast copy() { + return new BrotherhoodOutcast(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrotherhoodScribe.java b/Mage.Sets/src/mage/cards/b/BrotherhoodScribe.java new file mode 100644 index 00000000000..af932033555 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrotherhoodScribe.java @@ -0,0 +1,84 @@ +package mage.cards.b; + +import java.util.Objects; +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.condition.common.MetalcraftCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.hint.common.MetalcraftHint; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author Cguy7777 + */ +public final class BrotherhoodScribe extends CardImpl { + + public BrotherhoodScribe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Metalcraft -- {T}: You get {E}. Activate only if you control three or more artifacts. + this.addAbility(new ConditionalActivatedAbility( + new GetEnergyCountersControllerEffect(1), + new TapSourceCost(), + MetalcraftCondition.instance) + .setAbilityWord(AbilityWord.METALCRAFT) + .addHint(MetalcraftHint.instance)); + + // Whenever you get one or more {E} during your turn, creatures you control get +1/+1 until end of turn. + this.addAbility(new BrotherhoodScribeAbility()); + } + + private BrotherhoodScribe(final BrotherhoodScribe card) { + super(card); + } + + @Override + public BrotherhoodScribe copy() { + return new BrotherhoodScribe(this); + } +} + +class BrotherhoodScribeAbility extends TriggeredAbilityImpl { + + BrotherhoodScribeAbility() { + super(Zone.BATTLEFIELD, new BoostControlledEffect(1, 1, Duration.EndOfTurn)); + setTriggerPhrase("Whenever you get one or more {E} during your turn, "); + } + + private BrotherhoodScribeAbility(final BrotherhoodScribeAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_ADDED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getData().equals(CounterType.ENERGY.getName()) && game.isActivePlayer(this.getControllerId())) { + return Objects.equals(event.getTargetId(), this.getControllerId()); + } + return false; + } + + @Override + public BrotherhoodScribeAbility copy() { + return new BrotherhoodScribeAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrutalCathar.java b/Mage.Sets/src/mage/cards/b/BrutalCathar.java index 12eb86f0834..a52ad602d22 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalCathar.java +++ b/Mage.Sets/src/mage/cards/b/BrutalCathar.java @@ -30,7 +30,8 @@ public final class BrutalCathar extends CardImpl { // When this creature enters the battlefield or transforms into Brutal Cathar, exile target creature an opponent controls until this creature leaves the battlefield. Ability ability = new TransformsOrEntersTriggeredAbility( - new ExileUntilSourceLeavesEffect(), false + new ExileUntilSourceLeavesEffect() + .setText("exile target creature an opponent controls until this creature leaves the battlefield"), false ).setTriggerPhrase("When this creature enters the battlefield or transforms into {this}, "); ability.addTarget(new TargetOpponentsCreaturePermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/b/BuildersBane.java b/Mage.Sets/src/mage/cards/b/BuildersBane.java index 1fd01a0f3ae..4b038804802 100644 --- a/Mage.Sets/src/mage/cards/b/BuildersBane.java +++ b/Mage.Sets/src/mage/cards/b/BuildersBane.java @@ -73,7 +73,7 @@ class BuildersBaneEffect extends OneShotEffect { Map destroyedArtifactPerPlayer = new HashMap<>(); // Destroy X target artifacts. - for (UUID targetID : this.targetPointer.getTargets(game, source)) { + for (UUID targetID : this.getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetID); if (permanent != null) { if (permanent.destroy(source, game, false)) { diff --git a/Mage.Sets/src/mage/cards/b/Bulwark.java b/Mage.Sets/src/mage/cards/b/Bulwark.java index 9ea0ae852ab..cc2c9e5b825 100644 --- a/Mage.Sets/src/mage/cards/b/Bulwark.java +++ b/Mage.Sets/src/mage/cards/b/Bulwark.java @@ -58,7 +58,7 @@ class BulwarkDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (opponent != null && controller != null) { int amount = controller.getHand().size() - opponent.getHand().size(); diff --git a/Mage.Sets/src/mage/cards/b/BuriedInTheGarden.java b/Mage.Sets/src/mage/cards/b/BuriedInTheGarden.java new file mode 100644 index 00000000000..a5ec94fedd4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BuriedInTheGarden.java @@ -0,0 +1,61 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.mana.AddManaAnyColorAttachedControllerEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.mana.EnchantedTappedTriggeredManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.common.FilterNonlandPermanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class BuriedInTheGarden extends CardImpl { + + private static final FilterNonlandPermanent filter = new FilterNonlandPermanent("nonland permanent you don't control"); + static { + filter.add(TargetController.NOT_YOU.getControllerPredicate()); + } + + public BuriedInTheGarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant land + TargetPermanent auraTarget = new TargetLandPermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Buried in the Garden enters the battlefield, exile target nonland permanent you don't control until Buried in the Garden leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Whenever enchanted land is tapped for mana, its controller adds an additional one mana of any color. + this.addAbility(new EnchantedTappedTriggeredManaAbility(new AddManaAnyColorAttachedControllerEffect())); + + } + + private BuriedInTheGarden(final BuriedInTheGarden card) { + super(card); + } + + @Override + public BuriedInTheGarden copy() { + return new BuriedInTheGarden(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BurnAtTheStake.java b/Mage.Sets/src/mage/cards/b/BurnAtTheStake.java index 2ec78087c2b..b63e7d8bde5 100644 --- a/Mage.Sets/src/mage/cards/b/BurnAtTheStake.java +++ b/Mage.Sets/src/mage/cards/b/BurnAtTheStake.java @@ -69,13 +69,13 @@ class BurnAtTheStakeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int amount = (GetXValue.instance).calculate(game, source, this) * 3; - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(amount, source.getSourceId(), source, game, false, true); return true; } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(amount, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/b/BurningCinderFuryOfCrimsonChaosFire.java b/Mage.Sets/src/mage/cards/b/BurningCinderFuryOfCrimsonChaosFire.java index 99d0977adbf..da003aac2ba 100644 --- a/Mage.Sets/src/mage/cards/b/BurningCinderFuryOfCrimsonChaosFire.java +++ b/Mage.Sets/src/mage/cards/b/BurningCinderFuryOfCrimsonChaosFire.java @@ -162,9 +162,7 @@ class BurningCinderFuryOfCrimsonChaosFireCreatureGainControlEffect extends Conti @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (targetPointer != null) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); - } + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && controller != null) { return permanent.changeControllerId(controller, game, source); } diff --git a/Mage.Sets/src/mage/cards/b/ByrkeLongEarOfTheLaw.java b/Mage.Sets/src/mage/cards/b/ByrkeLongEarOfTheLaw.java new file mode 100644 index 00000000000..6ef66fb9b21 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/ByrkeLongEarOfTheLaw.java @@ -0,0 +1,63 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DoubleCountersTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class ByrkeLongEarOfTheLaw extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = + new FilterControlledCreaturePermanent("creature you control with a +1/+1 counter on it"); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + public ByrkeLongEarOfTheLaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{W}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.RABBIT, SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Byrke, Long Ear of the Law enters, put a +1/+1 counter on each of up to two target creatures. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on each of up to two target creatures") + ); + ability.addTarget(new TargetPermanent(0, 2, StaticFilters.FILTER_PERMANENT_CREATURES)); + this.addAbility(ability); + + // Whenever a creature you control with a +1/+1 counter on it attacks, double the number of +1/+1 counters on it. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility(new DoubleCountersTargetEffect(CounterType.P1P1), false, filter, true)); + } + + private ByrkeLongEarOfTheLaw(final ByrkeLongEarOfTheLaw card) { + super(card); + } + + @Override + public ByrkeLongEarOfTheLaw copy() { + return new ByrkeLongEarOfTheLaw(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CabalTherapist.java b/Mage.Sets/src/mage/cards/c/CabalTherapist.java index 56315f5366f..7145b75bc30 100644 --- a/Mage.Sets/src/mage/cards/c/CabalTherapist.java +++ b/Mage.Sets/src/mage/cards/c/CabalTherapist.java @@ -78,7 +78,7 @@ class CabalTherapistDiscardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); diff --git a/Mage.Sets/src/mage/cards/c/CabalTherapy.java b/Mage.Sets/src/mage/cards/c/CabalTherapy.java index fb2ed1c4154..6b9eb15dfa2 100644 --- a/Mage.Sets/src/mage/cards/c/CabalTherapy.java +++ b/Mage.Sets/src/mage/cards/c/CabalTherapy.java @@ -61,7 +61,7 @@ class CabalTherapyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); diff --git a/Mage.Sets/src/mage/cards/c/CaesarLegionsEmperor.java b/Mage.Sets/src/mage/cards/c/CaesarLegionsEmperor.java new file mode 100644 index 00000000000..c412523ca1f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaesarLegionsEmperor.java @@ -0,0 +1,84 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Mode; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.*; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.SoldierTokenWithHaste; +import mage.target.common.TargetOpponent; + +/** + * @author Cguy7777 + */ +public final class CaesarLegionsEmperor extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(TokenPredicate.TRUE); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Creature tokens you control", xValue); + + public CaesarLegionsEmperor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever you attack, you may sacrifice another creature. When you do, choose two -- + // * Create two 1/1 red and white Soldier creature tokens with haste that are tapped and attacking. + ReflexiveTriggeredAbility triggeredAbility = new ReflexiveTriggeredAbility( + new CreateTokenEffect(new SoldierTokenWithHaste(), 2, true, true), false); + triggeredAbility.getModes().setMinModes(2); + triggeredAbility.getModes().setMaxModes(2); + + // * You draw a card and you lose 1 life. + Mode drawMode = new Mode(new DrawCardSourceControllerEffect(1, "you")); + drawMode.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + triggeredAbility.addMode(drawMode); + + // * Caesar, Legion's Emperor deals damage equal to the number of creature tokens you control to target opponent. + Mode damageMode = new Mode(new DamageTargetEffect(xValue) + .setText("{this} deals damage equal to the number of creature tokens you control to target opponent")); + damageMode.addTarget(new TargetOpponent()); + triggeredAbility.addMode(damageMode); + triggeredAbility.addHint(hint); + + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + new DoWhenCostPaid( + triggeredAbility, + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE), + "Sacrifice another creature?"), + 1).addHint(hint)); + } + + private CaesarLegionsEmperor(final CaesarLegionsEmperor card) { + super(card); + } + + @Override + public CaesarLegionsEmperor copy() { + return new CaesarLegionsEmperor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaetusSeaTyrantOfSegovia.java b/Mage.Sets/src/mage/cards/c/CaetusSeaTyrantOfSegovia.java index a83d1ba8917..6fb8f125b1b 100644 --- a/Mage.Sets/src/mage/cards/c/CaetusSeaTyrantOfSegovia.java +++ b/Mage.Sets/src/mage/cards/c/CaetusSeaTyrantOfSegovia.java @@ -13,7 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.common.TargetCreaturePermanent; @@ -25,11 +25,10 @@ import java.util.UUID; */ public final class CaetusSeaTyrantOfSegovia extends CardImpl { - private static final FilterCard filter = new FilterCard("noncreature spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("noncreature spells you cast"); static { filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - filter.add(Predicates.not(CardType.LAND.getPredicate())); filter.add(Predicates.not(new AbilityPredicate(ConvokeAbility.class))); // So there are not redundant copies being added to each card } diff --git a/Mage.Sets/src/mage/cards/c/CallForAid.java b/Mage.Sets/src/mage/cards/c/CallForAid.java new file mode 100644 index 00000000000..5f5249c4c10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CallForAid.java @@ -0,0 +1,165 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainControlAllEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @author spillnerdev, xenohedron + */ +public final class CallForAid extends CardImpl { + + public CallForAid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}"); + + // Gain control of all creatures target opponent controls until end of turn. Untap those creatures. They gain haste until end of turn. You can't attack that player this turn. You can't sacrifice those creatures this turn. + getSpellAbility().addTarget(new TargetOpponent()); + getSpellAbility().addEffect(new CallForAidEffect()); + } + + private CallForAid(final CallForAid card) { + super(card); + } + + @Override + public CallForAid copy() { + return new CallForAid(this); + } +} + +class CallForAidEffect extends OneShotEffect { + + CallForAidEffect() { + super(Outcome.Benefit); + this.staticText = "Gain control of all creatures target opponent controls until end of turn. " + + "Untap those creatures. They gain haste until end of turn. " + + "You can't attack that player this turn. You can't sacrifice those creatures this turn."; + } + + private CallForAidEffect(CallForAidEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (opponent == null) { + return false; + } + + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(opponent.getId())); + List opponentCreatures = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game); + filter = new FilterPermanent(); + filter.add(new PermanentReferenceInCollectionPredicate(opponentCreatures, game)); + + //"Gain control of all creatures target opponent controls until end of turn" + new GainControlAllEffect(Duration.EndOfTurn, filter).apply(game, source); + game.getState().processAction(game); + + //"Untap those creatures". + new UntapAllEffect(filter).apply(game, source); + + //"They gain haste until end of turn" + game.addEffect(new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filter), source); + + //"You can't attack that player this turn" + game.addEffect(new CallForAidCantAttackThatPlayerEffect().setTargetPointer(this.getTargetPointer().copy()), source); + + //"You can't sacrifice those creatures this turn" + game.addEffect(new CallForAidYouCantSacrificeEffect(source.getControllerId(), filter), source); + + return true; + } + + @Override + public CallForAidEffect copy() { + return new CallForAidEffect(this); + } +} + +class CallForAidCantAttackThatPlayerEffect extends RestrictionEffect { + + CallForAidCantAttackThatPlayerEffect() { + super(Duration.EndOfTurn, Outcome.Detriment); + } + + private CallForAidCantAttackThatPlayerEffect(final CallForAidCantAttackThatPlayerEffect effect) { + super(effect); + } + + @Override + public CallForAidCantAttackThatPlayerEffect copy() { + return new CallForAidCantAttackThatPlayerEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + // Effect applies to all permanents (most likely creatures) controlled by the caster of "Call for Aid" + return Objects.equals(source.getControllerId(), permanent.getControllerId()); + } + + @Override + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) { + if (defenderId == null || attacker == null) { + return true; + } + // Should be only a single target in case of "Call for Aid" + return !this.getTargetPointer().getTargets(game,source).contains(defenderId); + } + +} + +class CallForAidYouCantSacrificeEffect extends ContinuousEffectImpl { + + private final UUID playerId; // controller who is prevented from sacrificing + private final FilterPermanent filter; // filter containing permanents that can't be sacrificed + + CallForAidYouCantSacrificeEffect(UUID playerId, FilterPermanent filter) { + super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + this.playerId = playerId; + this.filter = filter; + } + + private CallForAidYouCantSacrificeEffect(final CallForAidYouCantSacrificeEffect effect) { + super(effect); + this.playerId = effect.playerId; + this.filter = effect.filter; + } + + @Override + public CallForAidYouCantSacrificeEffect copy() { + return new CallForAidYouCantSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + if (permanent.getControllerId().equals(playerId)) { + permanent.setCanBeSacrificed(false); + } + } + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/c/CallToArms.java b/Mage.Sets/src/mage/cards/c/CallToArms.java index 6d21e7cd45c..104f789b443 100644 --- a/Mage.Sets/src/mage/cards/c/CallToArms.java +++ b/Mage.Sets/src/mage/cards/c/CallToArms.java @@ -115,6 +115,8 @@ class CallToArmsStateTriggeredAbility extends StateTriggeredAbility { public CallToArmsStateTriggeredAbility() { super(Zone.BATTLEFIELD, new SacrificeSourceEffect()); + setTriggerPhrase("When the chosen color isn't the most common color among nontoken permanents " + + "the chosen player controls or is tied for most common, "); } private CallToArmsStateTriggeredAbility(final CallToArmsStateTriggeredAbility ability) { @@ -141,11 +143,4 @@ class CallToArmsStateTriggeredAbility extends StateTriggeredAbility { return false; } - @Override - public String getRule() { - return "When the chosen color isn't the most common color " - + "among nontoken permanents the chosen player controls " - + "or is tied for most common, sacrifice {this}"; - } - } diff --git a/Mage.Sets/src/mage/cards/c/Camouflage.java b/Mage.Sets/src/mage/cards/c/Camouflage.java index 91fb8397724..5444e3fcd1a 100644 --- a/Mage.Sets/src/mage/cards/c/Camouflage.java +++ b/Mage.Sets/src/mage/cards/c/Camouflage.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.ArrayList; @@ -19,9 +18,9 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.PermanentInListPredicate; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.BlockerDeclaredEvent; @@ -119,7 +118,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { spentBlockers.add(possibleBlocker); } } - filter.add(Predicates.not(new PermanentInListPredicate(spentBlockers))); + filter.add(Predicates.not(new PermanentReferenceInCollectionPredicate(spentBlockers, game))); } if (defender.chooseUse(Outcome.Neutral, "Make a new blocker pile? If not, all remaining piles stay empty. (remaining piles: " + (attackerCount - masterList.size()) + ')', source, game)) { Target target = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, filter, true); diff --git a/Mage.Sets/src/mage/cards/c/CapitalOffense.java b/Mage.Sets/src/mage/cards/c/CapitalOffense.java index a6209f158a8..47c1722a577 100644 --- a/Mage.Sets/src/mage/cards/c/CapitalOffense.java +++ b/Mage.Sets/src/mage/cards/c/CapitalOffense.java @@ -83,7 +83,7 @@ enum capitaloffensecount implements DynamicValue { return 0; } - Card card = cardsInfo.get(0).getCard(); + Card card = cardsInfo.get(0).createCard(); if (card == null) { return 0; } diff --git a/Mage.Sets/src/mage/cards/c/CaptainOfTheMists.java b/Mage.Sets/src/mage/cards/c/CaptainOfTheMists.java index f36c76f7e41..5e06376e2e6 100644 --- a/Mage.Sets/src/mage/cards/c/CaptainOfTheMists.java +++ b/Mage.Sets/src/mage/cards/c/CaptainOfTheMists.java @@ -16,7 +16,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; @@ -27,7 +27,7 @@ import mage.target.TargetPermanent; */ public final class CaptainOfTheMists extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another Human"); + private static final FilterPermanent filter = new FilterControlledPermanent("another Human"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/c/CaptivatingVampire.java b/Mage.Sets/src/mage/cards/c/CaptivatingVampire.java index 6a365c6735b..9f9c542c323 100644 --- a/Mage.Sets/src/mage/cards/c/CaptivatingVampire.java +++ b/Mage.Sets/src/mage/cards/c/CaptivatingVampire.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.MageInt; @@ -11,12 +10,12 @@ import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -27,7 +26,7 @@ import java.util.UUID; public final class CaptivatingVampire extends CardImpl { private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent("Vampire creatures"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("untapped Vampires you control"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("untapped Vampires you control"); static { filter1.add(SubType.VAMPIRE.getPredicate()); @@ -46,7 +45,7 @@ public final class CaptivatingVampire extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter1, true))); // Tap five untapped Vampires you control: Gain control of target creature. It becomes a Vampire in addition to its other types. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CaptivatingVampireEffect(), new TapTargetCost(new TargetControlledCreaturePermanent(5, 5, filter2, true))); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CaptivatingVampireEffect(), new TapTargetCost(new TargetControlledPermanent(5, 5, filter2, true))); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/CaribouRange.java b/Mage.Sets/src/mage/cards/c/CaribouRange.java index 69721ec5449..fb14322c026 100644 --- a/Mage.Sets/src/mage/cards/c/CaribouRange.java +++ b/Mage.Sets/src/mage/cards/c/CaribouRange.java @@ -17,12 +17,11 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.permanent.token.CaribouToken; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -30,7 +29,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class CaribouRange extends CardImpl { - static FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Caribou token"); + static FilterControlledPermanent filter = new FilterControlledPermanent("a Caribou token"); static { filter.add(TokenPredicate.TRUE); diff --git a/Mage.Sets/src/mage/cards/c/CarrionImp.java b/Mage.Sets/src/mage/cards/c/CarrionImp.java index 0804414d36b..0f7c861de3d 100644 --- a/Mage.Sets/src/mage/cards/c/CarrionImp.java +++ b/Mage.Sets/src/mage/cards/c/CarrionImp.java @@ -10,8 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInGraveyard; import java.util.UUID; @@ -21,8 +20,6 @@ import java.util.UUID; */ public final class CarrionImp extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public CarrionImp(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); @@ -36,7 +33,7 @@ public final class CarrionImp extends CardImpl { // When Carrion Imp enters the battlefield, you may exile target creature card from a graveyard. If you do, you gain 2 life. Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect(), true); ability.addEffect(new GainLifeEffect(2).concatBy("If you do,")); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheGatewayExpress.java b/Mage.Sets/src/mage/cards/c/CaseOfTheGatewayExpress.java new file mode 100644 index 00000000000..4ae43d9905d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheGatewayExpress.java @@ -0,0 +1,139 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.watchers.common.AttackedThisTurnWatcher; + +/** + * Case of the Gateway Express {1}{W} + * Enchantment - Case + * When this case enters the battlefield, choose target creature you don't control. Each creature you control deals 1 damage to that creature. + * To solve -- Three or more creatures attacked this turn. + * Solved -- Creatures you control get +1/+0. + * + * @author DominionSpy + */ +public final class CaseOfTheGatewayExpress extends CardImpl { + + public CaseOfTheGatewayExpress(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + this.subtype.add(SubType.CASE); + + // When this case enters the battlefield, choose target creature you don't control. Each creature you control deals 1 damage to that creature. + Ability initialAbility = new EntersBattlefieldTriggeredAbility( + new CaseOfTheGatewayExpressEffect()); + initialAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + // To solve -- Three or more creatures attacked this turn. + Condition toSolveCondition = new CaseOfTheGatewayExpressCondition(); + // Solved -- Creatures you control get +1/+0. + Ability solvedAbility = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield), + SolvedSourceCondition.SOLVED, "")); + + this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility) + .addHint(new CaseOfTheGatewayExpressHint(toSolveCondition))); + } + + private CaseOfTheGatewayExpress(final CaseOfTheGatewayExpress card) { + super(card); + } + + @Override + public CaseOfTheGatewayExpress copy() { + return new CaseOfTheGatewayExpress(this); + } +} + +class CaseOfTheGatewayExpressEffect extends OneShotEffect { + + CaseOfTheGatewayExpressEffect() { + super(Outcome.Damage); + staticText = "choose target creature you don't control. " + + "Each creature you control deals 1 damage to that creature."; + } + + private CaseOfTheGatewayExpressEffect(final CaseOfTheGatewayExpressEffect effect) { + super(effect); + } + + @Override + public CaseOfTheGatewayExpressEffect copy() { + return new CaseOfTheGatewayExpressEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + source.getControllerId(), source, game)) { + if (creature == null) { + continue; + } + permanent.damage(1, creature.getId(), source, game); + } + return true; + } +} + +class CaseOfTheGatewayExpressCondition implements Condition { + + @Override + public boolean apply(Game game, Ability ability) { + AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); + return watcher != null && watcher.getAttackedThisTurnCreatures().size() >= 3; + } + + @Override + public String toString() { + return "Three or more creatures attacked this turn"; + } +} + +class CaseOfTheGatewayExpressHint extends CaseSolvedHint { + + CaseOfTheGatewayExpressHint(Condition condition) { + super(condition); + } + + private CaseOfTheGatewayExpressHint(final CaseOfTheGatewayExpressHint hint) { + super(hint); + } + + @Override + public CaseOfTheGatewayExpressHint copy() { + return new CaseOfTheGatewayExpressHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int attacked = game.getState() + .getWatcher(AttackedThisTurnWatcher.class) + .getAttackedThisTurnCreatures().size(); + return "Creatures that attacked: " + attacked + " (need 3)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheGorgonsKiss.java b/Mage.Sets/src/mage/cards/c/CaseOfTheGorgonsKiss.java new file mode 100644 index 00000000000..72b23090cd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheGorgonsKiss.java @@ -0,0 +1,149 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.WasDealtDamageThisTurnPredicate; +import mage.game.Game; +import mage.game.permanent.token.TokenImpl; +import mage.target.TargetPermanent; +import mage.watchers.common.CardsPutIntoGraveyardWatcher; + +/** + * Case of the Gorgon's Kiss {B} + * Enchantment - Case + * When this Case enters the battlefield, destroy up to one target creature that was dealt damage this turn. + * To solve -- Three or more creature cards were put into graveyards from anywhere this turn. + * Solved -- This Case is a 4/4 Gorgon creature with deathtouch and lifelink in addition to its other types. + * + * @author DominionSpy + */ +public final class CaseOfTheGorgonsKiss extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature that was dealt damage this turn"); + + static { + filter.add(WasDealtDamageThisTurnPredicate.instance); + } + + public CaseOfTheGorgonsKiss(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}"); + + this.subtype.add(SubType.CASE); + + // When this Case enters the battlefield, destroy up to one target creature that was dealt damage this turn. + Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()) + .setTriggerPhrase("When this Case enters the battlefield, "); + initialAbility.addTarget(new TargetPermanent(0, 1, filter)); + // To solve -- Three or more creature cards were put into graveyards from anywhere this turn. + Condition toSolveCondition = new CaseOfTheGorgonsKissCondition(); + // Solved -- This Case is a 4/4 Gorgon creature with deathtouch and lifelink in addition to its other types. + Ability solvedAbility = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BecomesCreatureSourceEffect(new CaseOfTheGorgonsKissToken(), + CardType.ENCHANTMENT, Duration.WhileOnBattlefield), + SolvedSourceCondition.SOLVED, "") + .setText("This Case is a 4/4 Gorgon creature with deathtouch and lifelink in addition to its other types.")); + + this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility) + .addHint(new CaseOfTheGorgonsKissHint(toSolveCondition)), + new CardsPutIntoGraveyardWatcher()); + } + + private CaseOfTheGorgonsKiss(final CaseOfTheGorgonsKiss card) { + super(card); + } + + @Override + public CaseOfTheGorgonsKiss copy() { + return new CaseOfTheGorgonsKiss(this); + } +} + +class CaseOfTheGorgonsKissCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class); + return watcher != null && + watcher.getCardsPutIntoGraveyardFromAnywhere(game) + .stream() + .filter(MageObject::isCreature) + .count() >= 3; + } + + @Override + public String toString() { + return "Three or more creature cards were put into graveyards from anywhere this turn"; + } +} + +class CaseOfTheGorgonsKissHint extends CaseSolvedHint { + + CaseOfTheGorgonsKissHint(Condition condition) { + super(condition); + } + + private CaseOfTheGorgonsKissHint(final CaseOfTheGorgonsKissHint hint) { + super(hint); + } + + @Override + public CaseOfTheGorgonsKissHint copy() { + return new CaseOfTheGorgonsKissHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int creatures = (int)game.getState() + .getWatcher(CardsPutIntoGraveyardWatcher.class) + .getCardsPutIntoGraveyardFromAnywhere(game) + .stream() + .filter(MageObject::isCreature) + .count(); + return "Creatures put into graveyards: " + creatures + " (need 3)."; + } +} + +class CaseOfTheGorgonsKissToken extends TokenImpl { + + CaseOfTheGorgonsKissToken() { + super("", "4/4 Gorgon creature with deathtouch and lifelink"); + this.cardType.add(CardType.CREATURE); + + this.subtype.add(SubType.GORGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.addAbility(DeathtouchAbility.getInstance()); + this.addAbility(LifelinkAbility.getInstance()); + } + + private CaseOfTheGorgonsKissToken(final CaseOfTheGorgonsKissToken token) { + super(token); + } + + @Override + public CaseOfTheGorgonsKissToken copy() { + return new CaseOfTheGorgonsKissToken(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheRansackedLab.java b/Mage.Sets/src/mage/cards/c/CaseOfTheRansackedLab.java new file mode 100644 index 00000000000..9e6a71c503d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheRansackedLab.java @@ -0,0 +1,143 @@ +package mage.cards.c; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.WatcherScope; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author DominionSpy + */ +public final class CaseOfTheRansackedLab extends CardImpl { + + private static final FilterCard filter = new FilterCard("Instant and sorcery spells"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate() + )); + } + + public CaseOfTheRansackedLab(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.subtype.add(SubType.CASE); + + // Instant and sorcery spells you cast cost {1} less to cast. + Ability initialAbility = new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1)); + // To solve -- You've cast four or more instant and sorcery spells this turn. + // Solved -- Whenever you cast an instant or sorcery spell, draw a card. + Ability solvedAbility = new ConditionalTriggeredAbility( + new SpellCastControllerTriggeredAbility(new DrawCardSourceControllerEffect(1), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false), + SolvedSourceCondition.SOLVED, ""); + + this.addAbility(new CaseAbility(initialAbility, CaseOfTheRansackedLabCondition.instance, solvedAbility) + .addHint(new CaseOfTheRansackedLabHint(CaseOfTheRansackedLabCondition.instance)), + new CaseOfTheRansackedLabWatcher()); + } + + private CaseOfTheRansackedLab(final CaseOfTheRansackedLab card) { + super(card); + } + + @Override + public CaseOfTheRansackedLab copy() { + return new CaseOfTheRansackedLab(this); + } +} + +class CaseOfTheRansackedLabWatcher extends Watcher { + + private final Map instantSorceryCount = new HashMap<>(); + + CaseOfTheRansackedLabWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell == null || !spell.isInstantOrSorcery(game)) { + return; + } + this.instantSorceryCount.putIfAbsent(spell.getControllerId(), 0); + this.instantSorceryCount.compute( + spell.getControllerId(), (k, a) -> a + 1 + ); + } + } + + @Override + public void reset() { + super.reset(); + this.instantSorceryCount.clear(); + } + + int getInstantSorceryCount(UUID playerId) { + return this.instantSorceryCount.getOrDefault(playerId, 0); + } +} + +enum CaseOfTheRansackedLabCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + CaseOfTheRansackedLabWatcher watcher = game.getState().getWatcher(CaseOfTheRansackedLabWatcher.class); + return watcher != null && watcher.getInstantSorceryCount(source.getControllerId()) > 3; + } + + @Override + public String toString() { + return "You've cast four or more instant and sorcery spells this turn"; + } +} + +class CaseOfTheRansackedLabHint extends CaseSolvedHint { + + CaseOfTheRansackedLabHint(Condition condition) { + super(condition); + } + + private CaseOfTheRansackedLabHint(final CaseOfTheRansackedLabHint hint) { + super(hint); + } + + @Override + public CaseOfTheRansackedLabHint copy() { + return new CaseOfTheRansackedLabHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int spells = game.getState().getWatcher(CaseOfTheRansackedLabWatcher.class) + .getInstantSorceryCount(ability.getControllerId()); + return "Instant and sorcery spells cast: " + spells + " (need 4)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheShatteredPact.java b/Mage.Sets/src/mage/cards/c/CaseOfTheShatteredPact.java new file mode 100644 index 00000000000..13ee5c400ba --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheShatteredPact.java @@ -0,0 +1,116 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author DominionSpy + */ +public final class CaseOfTheShatteredPact extends CardImpl { + + public CaseOfTheShatteredPact(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}"); + + this.subtype.add(SubType.CASE); + + // When this Case enters the battlefield, search your library for a basic land card, reveal it, put it into your hand, then shuffle. + Ability initialAbility = new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true)); + // To solve -- There are five colors among permanents you control. + // Solved -- At the beginning of combat on your turn, target creature you control gains flying, double strike, and vigilance until end of turn. + TriggeredAbility triggeredAbility = new BeginningOfCombatTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance()) + .setText("target creature you control gains flying"), + TargetController.YOU, false); + triggeredAbility.addEffect(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance()) + .setText(", double strike,")); + triggeredAbility.addEffect(new GainAbilityTargetEffect(VigilanceAbility.getInstance()) + .setText("and vigilance until end of turn.")); + triggeredAbility.addTarget(new TargetControlledCreaturePermanent()); + Ability solvedAbility = new ConditionalTriggeredAbility( + triggeredAbility, SolvedSourceCondition.SOLVED, ""); + + this.addAbility(new CaseAbility(initialAbility, CaseOfTheShatteredPactCondition.instance, solvedAbility) + .addHint(new CaseOfTheShatteredPactHint(CaseOfTheShatteredPactCondition.instance))); + } + + private CaseOfTheShatteredPact(final CaseOfTheShatteredPact card) { + super(card); + } + + @Override + public CaseOfTheShatteredPact copy() { + return new CaseOfTheShatteredPact(this); + } +} + +enum CaseOfTheShatteredPactCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + ObjectColor color = new ObjectColor(""); + game.getBattlefield() + .getAllActivePermanents(source.getControllerId()) + .stream() + .map(permanent -> permanent.getColor(game)) + .forEach(color::addColor); + return color.getColorCount() == 5; + } + + @Override + public String toString() { + return "There are five colors among permanents you control"; + } +} + +class CaseOfTheShatteredPactHint extends CaseSolvedHint { + + CaseOfTheShatteredPactHint(Condition condition) { + super(condition); + } + + CaseOfTheShatteredPactHint(final CaseOfTheShatteredPactHint hint) { + super(hint); + } + + @Override + public CaseOfTheShatteredPactHint copy() { + return new CaseOfTheShatteredPactHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + ObjectColor color = new ObjectColor(""); + game.getBattlefield() + .getAllActivePermanents(ability.getControllerId()) + .stream() + .map(permanent -> permanent.getColor(game)) + .forEach(color::addColor); + return "Colors: " + color.getColorCount() + " (need 5)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheShiftingVisage.java b/Mage.Sets/src/mage/cards/c/CaseOfTheShiftingVisage.java new file mode 100644 index 00000000000..29300aa33ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheShiftingVisage.java @@ -0,0 +1,88 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureSpell; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class CaseOfTheShiftingVisage extends CardImpl { + + private static final FilterCreatureSpell filter = new FilterCreatureSpell("a nonlegendary creature spell"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public CaseOfTheShiftingVisage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{U}"); + this.subtype.add(SubType.CASE); + + // At the beginning of your upkeep, surveil 1. + Ability initialAbility = new BeginningOfUpkeepTriggeredAbility(new SurveilEffect(1, false), TargetController.YOU, false); + // To solve — There are fifteen or more cards in your graveyard. + Condition toSolveCondition = new CardsInControllerGraveyardCondition(15); + // Solved — Whenever you cast a nonlegendary creature spell, copy that spell. + Ability solvedAbility = new ConditionalTriggeredAbility(new SpellCastControllerTriggeredAbility( + new CopyTargetSpellEffect(true).setText("copy that spell. (The copy becomes a token.)"), filter, false, SetTargetPointer.SPELL + ), SolvedSourceCondition.SOLVED, null); + + this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility) + .addHint(new CaseOfTheShiftingVisageHint(toSolveCondition))); + } + + private CaseOfTheShiftingVisage(final CaseOfTheShiftingVisage card) { + super(card); + } + + @Override + public CaseOfTheShiftingVisage copy() { + return new CaseOfTheShiftingVisage(this); + } +} + +class CaseOfTheShiftingVisageHint extends CaseSolvedHint { + + CaseOfTheShiftingVisageHint(Condition condition) { + super(condition); + } + + private CaseOfTheShiftingVisageHint(final CaseOfTheShiftingVisageHint hint) { + super(hint); + } + + @Override + public CaseOfTheShiftingVisageHint copy() { + return new CaseOfTheShiftingVisageHint(this); + } + + @Override + public String getConditionText(Game game, Ability source) { + UUID playerId = source.getControllerId(); + Player player = game.getPlayer(playerId); + if (player == null) { + return ""; + } + int value = player.getGraveyard().count(StaticFilters.FILTER_CARD, playerId, source, game); + return "Cards in graveyard: " + value + " (need 15)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheStashedSkeleton.java b/Mage.Sets/src/mage/cards/c/CaseOfTheStashedSkeleton.java new file mode 100644 index 00000000000..0759772440d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheStashedSkeleton.java @@ -0,0 +1,127 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TimingRule; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.SuspectedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.SkeletonToken2; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author DominionSpy + */ +public final class CaseOfTheStashedSkeleton extends CardImpl { + + static final FilterPermanent filter = new FilterControlledPermanent(SubType.SKELETON, "You control no suspected Skeletons"); + + static { + filter.add(SuspectedPredicate.instance); + } + + public CaseOfTheStashedSkeleton(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + + this.subtype.add(SubType.CASE); + + // When this Case enters the battlefield, create a 2/1 black Skeleton creature token and suspect it. + Ability initialAbility = new EntersBattlefieldTriggeredAbility( + new CaseOfTheStashedSkeletonEffect()); + // To solve -- You control no suspected Skeletons. + Condition toSolveCondition = new PermanentsOnTheBattlefieldCondition( + filter, ComparisonType.EQUAL_TO, 0, true); + // Solved -- {1}{B}, Sacrifice this Case: Search your library for a card, put it into your hand, then shuffle. Activate only as a sorcery. + Ability solvedAbility = new ConditionalActivatedAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(), false) + .setText("Search your library for a card, put it into your hand, then shuffle. Activate only as a sorcery."), + new ManaCostsImpl<>("{1}{B}"), + SolvedSourceCondition.SOLVED) + .setTiming(TimingRule.SORCERY); + solvedAbility.addCost(new SacrificeSourceCost().setText("sacrifice this Case")); + + this.addAbility(new CaseAbility(initialAbility, toSolveCondition, solvedAbility) + .addHint(new CaseOfTheStashedSkeletonHint(toSolveCondition))); + } + + private CaseOfTheStashedSkeleton(final CaseOfTheStashedSkeleton card) { + super(card); + } + + @Override + public CaseOfTheStashedSkeleton copy() { + return new CaseOfTheStashedSkeleton(this); + } +} + +class CaseOfTheStashedSkeletonEffect extends CreateTokenEffect { + + CaseOfTheStashedSkeletonEffect() { + super(new SkeletonToken2()); + staticText = "create a 2/1 black Skeleton creature token and suspect it"; + } + + private CaseOfTheStashedSkeletonEffect(final CaseOfTheStashedSkeletonEffect effect) { + super(effect); + } + + @Override + public CaseOfTheStashedSkeletonEffect copy() { + return new CaseOfTheStashedSkeletonEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + super.apply(game, source); + Permanent token = game.getPermanent(this.getLastAddedTokenIds().stream().findFirst().orElse(null)); + if (token != null) { + token.setSuspected(true, game, source); + return true; + } + return false; + } +} + +class CaseOfTheStashedSkeletonHint extends CaseSolvedHint { + + CaseOfTheStashedSkeletonHint(Condition condition) { + super(condition); + } + + private CaseOfTheStashedSkeletonHint(final CaseOfTheStashedSkeletonHint hint) { + super(hint); + } + + @Override + public CaseOfTheStashedSkeletonHint copy() { + return new CaseOfTheStashedSkeletonHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int suspectedSkeletons = game.getBattlefield() + .count(CaseOfTheStashedSkeleton.filter, ability.getControllerId(), + ability, game); + return "Suspected skeletons: " + suspectedSkeletons + " (need 0)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java new file mode 100644 index 00000000000..974b01cdfca --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java @@ -0,0 +1,97 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.FormidableCondition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.counter.DistributeCountersEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.abilities.keyword.TrampleAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetAttackingCreature; +import mage.target.common.TargetPermanentAmount; + +/** + * + * @author DominionSpy + */ +public final class CaseOfTheTrampledGarden extends CardImpl { + + public CaseOfTheTrampledGarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); + + this.subtype.add(SubType.CASE); + + // When this Case enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. + Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 2, + "one or two target creatures you control")); + TargetPermanentAmount target = new TargetPermanentAmount(2, StaticFilters.FILTER_CONTROLLED_CREATURES); + target.setMinNumberOfTargets(1); + initialAbility.addTarget(target); + // To solve -- Creatures you control have total power 8 or greater. + // Solved -- Whenever you attack, put a +1/+1 counter on target attacking creature. It gains trample until end of turn. + Ability solvedAbility = new ConditionalTriggeredAbility( + new AttacksWithCreaturesTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1), + SolvedSourceCondition.SOLVED, ""); + solvedAbility.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()) + .setText("it gains trample until end of turn")); + solvedAbility.addTarget(new TargetAttackingCreature()); + + this.addAbility(new CaseAbility(initialAbility, FormidableCondition.instance, solvedAbility) + .addHint(new CaseOfTheTrampledGardenHint(FormidableCondition.instance))); + } + + private CaseOfTheTrampledGarden(final CaseOfTheTrampledGarden card) { + super(card); + } + + @Override + public CaseOfTheTrampledGarden copy() { + return new CaseOfTheTrampledGarden(this); + } +} + +class CaseOfTheTrampledGardenHint extends CaseSolvedHint { + + CaseOfTheTrampledGardenHint(Condition condition) { + super(condition); + } + + private CaseOfTheTrampledGardenHint(final CaseOfTheTrampledGardenHint hint) { + super(hint); + } + + @Override + public CaseOfTheTrampledGardenHint copy() { + return new CaseOfTheTrampledGardenHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int power = game.getBattlefield() + .getAllActivePermanents(new FilterCreaturePermanent(), ability.getControllerId(), game) + .stream() + .map(Permanent::getPower) + .map(MageInt::getValue) + .reduce(0, Integer::sum); + return "Total power: " + power + " (need 8)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheUneatenFeast.java b/Mage.Sets/src/mage/cards/c/CaseOfTheUneatenFeast.java new file mode 100644 index 00000000000..b3d70b4a139 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheUneatenFeast.java @@ -0,0 +1,155 @@ +package mage.cards.c; + +import java.util.Objects; +import java.util.UUID; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.CaseAbility; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.MayCastFromGraveyardSourceAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SolvedSourceCondition; +import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.common.CaseSolvedHint; +import mage.cards.Card; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.watchers.common.PlayerGainedLifeWatcher; + +/** + * + * @author DominionSpy + */ +public final class CaseOfTheUneatenFeast extends CardImpl { + + private static final Condition condition = new YouGainedLifeCondition(ComparisonType.MORE_THAN, 4){ + @Override + public String toString() { + return "you've gained 5 or more life this turn"; + } + }; + + public CaseOfTheUneatenFeast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + this.subtype.add(SubType.CASE); + + // Whenever a creature enters the battlefield under your control, you gain 1 life. + Ability initialAbility = new EntersBattlefieldControlledTriggeredAbility( + new GainLifeEffect(1), StaticFilters.FILTER_PERMANENT_CREATURE); + // To solve -- You've gained 5 or more life this turn. + // Solved -- Sacrifice this Case: Creature cards in your graveyard gain "You may cast this card from your graveyard" until end of turn. + Ability solvedAbility = new ConditionalActivatedAbility( + new CaseOfTheUneatenFeastEffect(), + new SacrificeSourceCost().setText("sacrifice this Case"), + SolvedSourceCondition.SOLVED); + + this.addAbility(new CaseAbility(initialAbility, condition, solvedAbility) + .addHint(new CaseOfTheUneatenFeastHint(condition)), + new PlayerGainedLifeWatcher()); + } + + private CaseOfTheUneatenFeast(final CaseOfTheUneatenFeast card) { + super(card); + } + + @Override + public CaseOfTheUneatenFeast copy() { + return new CaseOfTheUneatenFeast(this); + } +} + +class CaseOfTheUneatenFeastEffect extends ContinuousEffectImpl { + + CaseOfTheUneatenFeastEffect() { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "Creature cards in your graveyard gain \"You may cast this card from your graveyard\" until end of turn"; + } + + private CaseOfTheUneatenFeastEffect(final CaseOfTheUneatenFeastEffect effect) { + super(effect); + } + + @Override + public CaseOfTheUneatenFeastEffect copy() { + return new CaseOfTheUneatenFeastEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + if (!this.affectedObjectsSet) { + return; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return; + } + player.getGraveyard() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .filter(card -> card.isCreature(game)) + .forEach(card -> affectedObjectList.add(new MageObjectReference(card, game))); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.getGraveyard() + .stream() + .filter(cardId -> affectedObjectList.contains(new MageObjectReference(cardId, game))) + .forEach(cardId -> { + Card card = game.getCard(cardId); + if (card == null) { + return; + } + MayCastFromGraveyardSourceAbility ability = new MayCastFromGraveyardSourceAbility(); + ability.setSourceId(cardId); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + }); + return true; + } +} + +class CaseOfTheUneatenFeastHint extends CaseSolvedHint { + + CaseOfTheUneatenFeastHint(Condition condition) { + super(condition); + } + + private CaseOfTheUneatenFeastHint(final CaseOfTheUneatenFeastHint hint) { + super(hint); + } + + @Override + public CaseOfTheUneatenFeastHint copy() { + return new CaseOfTheUneatenFeastHint(this); + } + + @Override + public String getConditionText(Game game, Ability ability) { + int lifeGained = game.getState().getWatcher(PlayerGainedLifeWatcher.class) + .getLifeGained(ability.getControllerId()); + return "Life gained: " + lifeGained + " (need 5)."; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CastThroughTime.java b/Mage.Sets/src/mage/cards/c/CastThroughTime.java index 0b600335f76..07bee7a8cc7 100644 --- a/Mage.Sets/src/mage/cards/c/CastThroughTime.java +++ b/Mage.Sets/src/mage/cards/c/CastThroughTime.java @@ -6,7 +6,7 @@ import mage.abilities.keyword.ReboundAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -17,7 +17,7 @@ import java.util.UUID; */ public final class CastThroughTime extends CardImpl { - private static final FilterCard filter = new FilterCard("Instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("Instant and sorcery spells you control"); static { filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate())); diff --git a/Mage.Sets/src/mage/cards/c/CatacombDragon.java b/Mage.Sets/src/mage/cards/c/CatacombDragon.java index eb8f7dcd63e..fbc8a66964a 100644 --- a/Mage.Sets/src/mage/cards/c/CatacombDragon.java +++ b/Mage.Sets/src/mage/cards/c/CatacombDragon.java @@ -73,7 +73,7 @@ class CatacombDragonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null || !permanent.isCreature(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/CatharsCrusade.java b/Mage.Sets/src/mage/cards/c/CatharsCrusade.java index d9cc61888ba..ce861932d65 100644 --- a/Mage.Sets/src/mage/cards/c/CatharsCrusade.java +++ b/Mage.Sets/src/mage/cards/c/CatharsCrusade.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; @@ -9,7 +8,6 @@ import mage.constants.CardType; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; import java.util.UUID; @@ -21,11 +19,10 @@ public final class CatharsCrusade extends CardImpl { public CatharsCrusade(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); - // Whenever a creature enters the battlefield under your control, put a +1/+1 counter on each creature you control. this.addAbility(new EntersBattlefieldControlledTriggeredAbility( Zone.BATTLEFIELD, - new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()), + new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE), StaticFilters.FILTER_PERMANENT_A_CREATURE, false) ); diff --git a/Mage.Sets/src/mage/cards/c/CavalcadeOfCalamity.java b/Mage.Sets/src/mage/cards/c/CavalcadeOfCalamity.java index 4a9c8dbb660..f5e4588da6b 100644 --- a/Mage.Sets/src/mage/cards/c/CavalcadeOfCalamity.java +++ b/Mage.Sets/src/mage/cards/c/CavalcadeOfCalamity.java @@ -64,7 +64,7 @@ class CavalcadeOfCalamityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { return game.damagePlayerOrPermanent( - game.getCombat().getDefenderId(targetPointer.getFirst(game, source)), 1, + game.getCombat().getDefenderId(getTargetPointer().getFirst(game, source)), 1, source.getSourceId(), source, game, false, true ) > 0; } diff --git a/Mage.Sets/src/mage/cards/c/CelestialAncient.java b/Mage.Sets/src/mage/cards/c/CelestialAncient.java index 1a671192f0e..5fbad38460b 100644 --- a/Mage.Sets/src/mage/cards/c/CelestialAncient.java +++ b/Mage.Sets/src/mage/cards/c/CelestialAncient.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -12,7 +11,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; /** * @@ -32,7 +30,7 @@ public final class CelestialAncient extends CardImpl { // Whenever you cast an enchantment spell, put a +1/+1 counter on each creature you control. this.addAbility(new SpellCastControllerTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), - new FilterControlledCreaturePermanent()), StaticFilters.FILTER_SPELL_AN_ENCHANTMENT, false)); + StaticFilters.FILTER_CONTROLLED_CREATURE), StaticFilters.FILTER_SPELL_AN_ENCHANTMENT, false)); } private CelestialAncient(final CelestialAncient card) { diff --git a/Mage.Sets/src/mage/cards/c/CemeteryReaper.java b/Mage.Sets/src/mage/cards/c/CemeteryReaper.java index b60ea4218be..73ceef64f44 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryReaper.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryReaper.java @@ -17,6 +17,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.game.permanent.token.ZombieToken; @@ -46,7 +47,7 @@ public final class CemeteryReaper extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), new ManaCostsImpl<>("{2}{B}")); ability.addCost(new TapSourceCost()); ability.addEffect(new CreateTokenEffect(new ZombieToken())); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/CemeteryRecruitment.java b/Mage.Sets/src/mage/cards/c/CemeteryRecruitment.java index a9e0bee55de..0366b73af26 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryRecruitment.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryRecruitment.java @@ -60,7 +60,7 @@ class CemeteryRecruitmentEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { if (controller.moveCards(card, Zone.HAND, source, game) && card.hasSubtype(SubType.ZOMBIE, game)) { diff --git a/Mage.Sets/src/mage/cards/c/CephalidSnitch.java b/Mage.Sets/src/mage/cards/c/CephalidSnitch.java index 3d544b65f96..334b8a8c050 100644 --- a/Mage.Sets/src/mage/cards/c/CephalidSnitch.java +++ b/Mage.Sets/src/mage/cards/c/CephalidSnitch.java @@ -81,7 +81,7 @@ class CephalidSnitchEffect extends LoseAbilityTargetEffect{ @Override public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { List toRemove = new ArrayList<>(); //Go through protection abilities and sort out any containing black diff --git a/Mage.Sets/src/mage/cards/c/ChainerDementiaMaster.java b/Mage.Sets/src/mage/cards/c/ChainerDementiaMaster.java index 84604079b71..6b4a0f82516 100644 --- a/Mage.Sets/src/mage/cards/c/ChainerDementiaMaster.java +++ b/Mage.Sets/src/mage/cards/c/ChainerDementiaMaster.java @@ -21,6 +21,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; @@ -56,7 +57,7 @@ public final class ChainerDementiaMaster extends CardImpl { // {B}{B}{B}, Pay 3 life: Put target creature card from a graveyard onto the battlefield under your control. That creature is black and is a Nightmare in addition to its other creature types. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ChainerDementiaMasterEffect(), new ManaCostsImpl<>("{B}{B}{B}")); ability.addCost(new PayLifeCost(3)); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); // When Chainer, Dementia Master leaves the battlefield, exile all Nightmares. diff --git a/Mage.Sets/src/mage/cards/c/ChampionsDrake.java b/Mage.Sets/src/mage/cards/c/ChampionsDrake.java index abda54e3306..3991b6049eb 100644 --- a/Mage.Sets/src/mage/cards/c/ChampionsDrake.java +++ b/Mage.Sets/src/mage/cards/c/ChampionsDrake.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -16,7 +15,7 @@ import mage.constants.ComparisonType; import mage.constants.Duration; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -38,7 +37,8 @@ public final class ChampionsDrake extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Champion's Drake gets +3/+3 as long as you control a creature with three or more level counters on it. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new BoostSourceEffect(3, 3, Duration.WhileOnBattlefield), new PermanentHasCounterCondition(CounterType.LEVEL, 2, new FilterControlledCreaturePermanent(), ComparisonType.MORE_THAN), rule); + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new BoostSourceEffect(3, 3, Duration.WhileOnBattlefield), + new PermanentHasCounterCondition(CounterType.LEVEL, 2, StaticFilters.FILTER_CONTROLLED_CREATURE, ComparisonType.MORE_THAN), rule); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); } diff --git a/Mage.Sets/src/mage/cards/c/ChampionsOfMinasTirith.java b/Mage.Sets/src/mage/cards/c/ChampionsOfMinasTirith.java index c50d98295c4..2b3b91eb68c 100644 --- a/Mage.Sets/src/mage/cards/c/ChampionsOfMinasTirith.java +++ b/Mage.Sets/src/mage/cards/c/ChampionsOfMinasTirith.java @@ -90,7 +90,7 @@ class ChampionsOfMinasTirithEffect extends OneShotEffect { new TargetPlayerCantAttackYouEffect(Duration.EndOfCombat), ManaUtil.createManaCost(CardsInTargetPlayerHandCount.instance, game, source, this), "Pay to be able to attack " + player.getName() + " this combat?" - ).setTargetPointer(targetPointer).apply(game, source); + ).setTargetPointer(this.getTargetPointer().copy()).apply(game, source); } } @@ -111,6 +111,6 @@ class ChampionsOfMinasTirithDoIfCostPaid extends DoIfCostPaid { @Override protected Player getPayingPlayer(Game game, Ability source) { - return game.getPlayer(targetPointer.getFirst(game, source)); + return game.getPlayer(getTargetPointer().getFirst(game, source)); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java index 99b33e592f6..4826388a400 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java +++ b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java @@ -120,13 +120,13 @@ class ChandraAblazeEffect2 extends OneShotEffect { public boolean apply(Game game, Ability source) { Card card = (Card) this.getValue("discardedCard"); if (card != null && card.getColor(game).isRed()) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(4, source.getSourceId(), source, game, false, true); return true; } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(4, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/c/ChandraGremlinWrangler.java b/Mage.Sets/src/mage/cards/c/ChandraGremlinWrangler.java index 6a9dd59690a..bacc5be6347 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraGremlinWrangler.java +++ b/Mage.Sets/src/mage/cards/c/ChandraGremlinWrangler.java @@ -14,7 +14,7 @@ import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.GremlinToken; -import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetCreatureOrPlayer; import java.util.UUID; @@ -41,9 +41,9 @@ public final class ChandraGremlinWrangler extends CardImpl { // +1: Create a 2/2 red Gremlin creature token. this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new GremlinToken()), 1)); - // -2: Chandra, Gremlin Wrangler deals X damage to any target, where X is the number of Gremlins you control. - Ability ability = new LoyaltyAbility(new DamageTargetEffect(xValue).setText("{this} deals X damage to any target, where X is the number of Gremlins you control."), -2); - ability.addTarget(new TargetAnyTarget()); + // -2: Chandra, Gremlin Wrangler deals X damage to target creature or player, where X is the number of Gremlins you control. + Ability ability = new LoyaltyAbility(new DamageTargetEffect(xValue).setText("{this} deals X damage to target creature or player, where X is the number of Gremlins you control."), -2); + ability.addTarget(new TargetCreatureOrPlayer()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/ChaosLord.java b/Mage.Sets/src/mage/cards/c/ChaosLord.java index 83b5c1422fe..69b94337662 100644 --- a/Mage.Sets/src/mage/cards/c/ChaosLord.java +++ b/Mage.Sets/src/mage/cards/c/ChaosLord.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.ControlsPermanentsComparedToOpponentsCondition; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -62,7 +62,7 @@ class ChaosLordTriggeredAbility extends BeginningOfUpkeepTriggeredAbility { ChaosLordTriggeredAbility() { super(Zone.BATTLEFIELD, - new GainControlSourceEffect(), + new TargetPlayerGainControlSourceEffect(), TargetController.YOU, false); } @@ -96,39 +96,11 @@ class ChaosLordTriggeredAbility extends BeginningOfUpkeepTriggeredAbility { } -class GainControlSourceEffect extends ContinuousEffectImpl { - - GainControlSourceEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "target opponent gains control of {this}"; - } - - private GainControlSourceEffect(final GainControlSourceEffect effect) { - super(effect); - } - - @Override - public GainControlSourceEffect copy() { - return new GainControlSourceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } -} - class ChaosLordEffect extends AsThoughEffectImpl { ChaosLordEffect() { super(AsThoughEffectType.ATTACK_AS_HASTE, Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "Chaos Lord can attack as though it had haste unless it entered the battlefield this turn"; + staticText = "{this} can attack as though it had haste unless it entered the battlefield this turn"; } private ChaosLordEffect(final ChaosLordEffect effect) { diff --git a/Mage.Sets/src/mage/cards/c/ChaosWarp.java b/Mage.Sets/src/mage/cards/c/ChaosWarp.java index 2dd3c89e098..e93589dbadb 100644 --- a/Mage.Sets/src/mage/cards/c/ChaosWarp.java +++ b/Mage.Sets/src/mage/cards/c/ChaosWarp.java @@ -62,7 +62,7 @@ class ChaosWarpShuffleIntoLibraryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { Player owner = game.getPlayer(permanent.getOwnerId()); if (owner != null) { @@ -93,7 +93,7 @@ class ChaosWarpRevealEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = (Permanent) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.BATTLEFIELD); + Permanent permanent = (Permanent) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.BATTLEFIELD); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java b/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java index 65c3354f910..c288aa6d30f 100644 --- a/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java +++ b/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java @@ -95,7 +95,7 @@ class ChargingCinderhornDamageTargetEffect extends OneShotEffect { } DynamicValue amount = new CountersSourceCount(CounterType.FURY); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(amount.calculate(game, source, this), source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/c/ChariotOfTheSun.java b/Mage.Sets/src/mage/cards/c/ChariotOfTheSun.java index 053725016e0..ce2056672af 100644 --- a/Mage.Sets/src/mage/cards/c/ChariotOfTheSun.java +++ b/Mage.Sets/src/mage/cards/c/ChariotOfTheSun.java @@ -71,7 +71,7 @@ class ChariotOfTheSunEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { this.discard(); return false; diff --git a/Mage.Sets/src/mage/cards/c/CharnelSerenade.java b/Mage.Sets/src/mage/cards/c/CharnelSerenade.java new file mode 100644 index 00000000000..999b003cc2c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CharnelSerenade.java @@ -0,0 +1,85 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellWithTimeCountersEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CharnelSerenade extends CardImpl { + + public CharnelSerenade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); + + // Surveil 3, then return a creature card from your graveyard to the battlefield with a finality counter on it. Exile Charnel Serenade with three time counters on it. + this.getSpellAbility().addEffect(new SurveilEffect(3, false)); + this.getSpellAbility().addEffect(new CharnelSerenadeEffect()); + this.getSpellAbility().addEffect(new ExileSpellWithTimeCountersEffect(3)); + + // Suspend 3--{2}{B} + this.addAbility(new SuspendAbility(3, new ManaCostsImpl<>("{2}{B}"), this)); + + } + + private CharnelSerenade(final CharnelSerenade card) { + super(card); + } + + @Override + public CharnelSerenade copy() { + return new CharnelSerenade(this); + } +} + +class CharnelSerenadeEffect extends OneShotEffect { + + CharnelSerenadeEffect() { + super(Outcome.Benefit); + staticText = ", then return a creature card from your graveyard to the battlefield with a finality counter on it"; + } + + private CharnelSerenadeEffect(final CharnelSerenadeEffect effect) { + super(effect); + } + + @Override + public CharnelSerenadeEffect copy() { + return new CharnelSerenadeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getGraveyard().count(StaticFilters.FILTER_CARD_CREATURE, game) < 1) { + return false; + } + TargetCard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + game.setEnterWithCounters(card.getId(), new Counters().addCounter(CounterType.FINALITY.createInstance())); + return player.moveCards(card, Zone.BATTLEFIELD, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CheckForTraps.java b/Mage.Sets/src/mage/cards/c/CheckForTraps.java index 53742e29855..fbbd677a08a 100644 --- a/Mage.Sets/src/mage/cards/c/CheckForTraps.java +++ b/Mage.Sets/src/mage/cards/c/CheckForTraps.java @@ -62,7 +62,7 @@ class CheckForTrapsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/ChickenALaKing.java b/Mage.Sets/src/mage/cards/c/ChickenALaKing.java index 42305112e96..bffc8641de0 100644 --- a/Mage.Sets/src/mage/cards/c/ChickenALaKing.java +++ b/Mage.Sets/src/mage/cards/c/ChickenALaKing.java @@ -13,7 +13,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -28,7 +28,7 @@ import java.util.UUID; */ public final class ChickenALaKing extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Bird you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Bird you control"); static { filter.add(TappedPredicate.UNTAPPED); diff --git a/Mage.Sets/src/mage/cards/c/ChiefEngineer.java b/Mage.Sets/src/mage/cards/c/ChiefEngineer.java index a98d749a0ed..9ab8cfdcb71 100644 --- a/Mage.Sets/src/mage/cards/c/ChiefEngineer.java +++ b/Mage.Sets/src/mage/cards/c/ChiefEngineer.java @@ -9,8 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; -import mage.filter.common.FilterArtifactCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -21,10 +20,10 @@ import java.util.UUID; */ public final class ChiefEngineer extends CardImpl { - private static final FilterCard filter = new FilterArtifactCard("artifact spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("artifact spells you cast"); static { - filter.add(Predicates.not(CardType.LAND.getPredicate())); + filter.add(CardType.ARTIFACT.getPredicate()); filter.add(Predicates.not(new AbilityPredicate(ConvokeAbility.class))); // So there are not redundant copies being added to each card } diff --git a/Mage.Sets/src/mage/cards/c/ChoArrimAlchemist.java b/Mage.Sets/src/mage/cards/c/ChoArrimAlchemist.java index 6f4666ba7f8..206174cb155 100644 --- a/Mage.Sets/src/mage/cards/c/ChoArrimAlchemist.java +++ b/Mage.Sets/src/mage/cards/c/ChoArrimAlchemist.java @@ -74,6 +74,7 @@ class ChoArrimAlchemistEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java b/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java index 46240137c61..80a0c602bff 100644 --- a/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java +++ b/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java @@ -62,7 +62,7 @@ class ChoiceOfDamnationsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { int numberPermanents = game.getState().getBattlefield().countAll(new FilterPermanent(), targetPlayer.getId(), game); diff --git a/Mage.Sets/src/mage/cards/c/CleansingBeam.java b/Mage.Sets/src/mage/cards/c/CleansingBeam.java index 0693f56b247..9ed4c04c41b 100644 --- a/Mage.Sets/src/mage/cards/c/CleansingBeam.java +++ b/Mage.Sets/src/mage/cards/c/CleansingBeam.java @@ -52,7 +52,7 @@ class CleansingBeamEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null) { ObjectColor color = target.getColor(game); target.damage(2, source, game); diff --git a/Mage.Sets/src/mage/cards/c/Clickslither.java b/Mage.Sets/src/mage/cards/c/Clickslither.java index ce1e01d9e29..b0dd80f30db 100644 --- a/Mage.Sets/src/mage/cards/c/Clickslither.java +++ b/Mage.Sets/src/mage/cards/c/Clickslither.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -17,8 +16,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.FilterPermanent; /** * @@ -26,11 +24,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class Clickslither extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.GOBLIN, "a Goblin"); public Clickslither(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{R}{R}{R}"); diff --git a/Mage.Sets/src/mage/cards/c/CloneLegion.java b/Mage.Sets/src/mage/cards/c/CloneLegion.java index 6c7266d122b..8787203eeae 100644 --- a/Mage.Sets/src/mage/cards/c/CloneLegion.java +++ b/Mage.Sets/src/mage/cards/c/CloneLegion.java @@ -60,7 +60,7 @@ class CloneLegionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && targetPlayer != null) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, targetPlayer.getId(), game)) { if (permanent != null) { diff --git a/Mage.Sets/src/mage/cards/c/CoercedConfession.java b/Mage.Sets/src/mage/cards/c/CoercedConfession.java index 2a19647e98c..5cb082bdd42 100644 --- a/Mage.Sets/src/mage/cards/c/CoercedConfession.java +++ b/Mage.Sets/src/mage/cards/c/CoercedConfession.java @@ -55,7 +55,7 @@ class CoercedConfessionMillEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/CoercedToKill.java b/Mage.Sets/src/mage/cards/c/CoercedToKill.java new file mode 100644 index 00000000000..8142d1417dc --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CoercedToKill.java @@ -0,0 +1,56 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.ControlEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.constants.*; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.effects.common.AttachEffect; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; + +/** + * @author Cguy7777 + */ +public final class CoercedToKill extends CardImpl { + + public CoercedToKill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.GainControl)); + this.addAbility(new EnchantAbility(auraTarget)); + + // You control enchanted creature. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ControlEnchantedEffect())); + + // Enchanted creature has base power and toughness 1/1, has deathtouch, and is an Assassin in addition to its other types. + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect(DeathtouchAbility.getInstance(), AttachmentType.AURA) + .setText(", has deathtouch")); + ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.ASSASSIN, AttachmentType.AURA) + .setText(", and is an Assassin in addition to its other types")); + this.addAbility(ability); + } + + private CoercedToKill(final CoercedToKill card) { + super(card); + } + + @Override + public CoercedToKill copy() { + return new CoercedToKill(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CoerciveRecruiter.java b/Mage.Sets/src/mage/cards/c/CoerciveRecruiter.java index 4dfd5f8f916..04f7ff99355 100644 --- a/Mage.Sets/src/mage/cards/c/CoerciveRecruiter.java +++ b/Mage.Sets/src/mage/cards/c/CoerciveRecruiter.java @@ -14,7 +14,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -24,7 +24,7 @@ import java.util.UUID; */ public final class CoerciveRecruiter extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.PIRATE, "Pirate"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.PIRATE, "Pirate"); public CoerciveRecruiter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); diff --git a/Mage.Sets/src/mage/cards/c/CoffinQueen.java b/Mage.Sets/src/mage/cards/c/CoffinQueen.java index 9b161be96c1..2a776d04eba 100644 --- a/Mage.Sets/src/mage/cards/c/CoffinQueen.java +++ b/Mage.Sets/src/mage/cards/c/CoffinQueen.java @@ -18,6 +18,7 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.events.GameEvent; @@ -45,7 +46,7 @@ public final class CoffinQueen extends CardImpl { // {2}{B}, {tap}: Put target creature card from a graveyard onto the battlefield under your control. When Coffin Queen becomes untapped or you lose control of Coffin Queen, exile that creature. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{2}{B}")); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); ability.addEffect(new CoffinQueenCreateDelayedTriggerEffect()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/ColdSnap.java b/Mage.Sets/src/mage/cards/c/ColdSnap.java index 4164d22096b..0d87c6a49c9 100644 --- a/Mage.Sets/src/mage/cards/c/ColdSnap.java +++ b/Mage.Sets/src/mage/cards/c/ColdSnap.java @@ -59,9 +59,9 @@ class ColdSnapDamageTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - int damage = game.getBattlefield().getAllActivePermanents(filter, targetPointer.getFirst(game, source), game).size(); + int damage = game.getBattlefield().getAllActivePermanents(filter, getTargetPointer().getFirst(game, source), game).size(); player.damage(damage, source.getSourceId(), source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/c/CollectiveDefiance.java b/Mage.Sets/src/mage/cards/c/CollectiveDefiance.java index e1980b0d26a..b38e425645d 100644 --- a/Mage.Sets/src/mage/cards/c/CollectiveDefiance.java +++ b/Mage.Sets/src/mage/cards/c/CollectiveDefiance.java @@ -86,7 +86,7 @@ class CollectiveDefianceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/ColonelAutumn.java b/Mage.Sets/src/mage/cards/c/ColonelAutumn.java new file mode 100644 index 00000000000..0ab47b67c5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ColonelAutumn.java @@ -0,0 +1,96 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.constants.*; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.ExploitAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * @author Cguy7777 + */ +public final class ColonelAutumn extends CardImpl { + + public ColonelAutumn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Exploit + this.addAbility(new ExploitAbility()); + + // Other legendary creatures you control have exploit. + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledEffect( + new ExploitAbility(), + Duration.WhileOnBattlefield, + StaticFilters.FILTER_CREATURES_LEGENDARY, + true))); + + // Whenever a creature you control exploits a creature, put a +1/+1 counter on each creature you control. + this.addAbility(new ColonelAutumnTriggeredAbility()); + } + + private ColonelAutumn(final ColonelAutumn card) { + super(card); + } + + @Override + public ColonelAutumn copy() { + return new ColonelAutumn(this); + } +} + +// Based on SkullSkaabTriggeredAbility +class ColonelAutumnTriggeredAbility extends TriggeredAbilityImpl { + + ColonelAutumnTriggeredAbility() { + super(Zone.BATTLEFIELD, + new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED)); + setTriggerPhrase("Whenever a creature you control exploits a creature, "); + } + + private ColonelAutumnTriggeredAbility(final ColonelAutumnTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.EXPLOITED_CREATURE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent exploiter = game.getPermanentOrLKIBattlefield(event.getSourceId()); + Permanent exploited = game.getPermanentOrLKIBattlefield(event.getTargetId()); + + return exploiter != null && exploited != null + && exploiter.isCreature(game) + && exploited.isCreature(game) + && exploiter.isControlledBy(this.getControllerId()); + } + + @Override + public ColonelAutumnTriggeredAbility copy() { + return new ColonelAutumnTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CombustibleGearhulk.java b/Mage.Sets/src/mage/cards/c/CombustibleGearhulk.java index 95de8138caa..5d3b806454e 100644 --- a/Mage.Sets/src/mage/cards/c/CombustibleGearhulk.java +++ b/Mage.Sets/src/mage/cards/c/CombustibleGearhulk.java @@ -111,7 +111,7 @@ class CombustibleGearhulkMillAndDamageEffect extends OneShotEffect { .stream() .mapToInt(MageObject::getManaValue) .sum(); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(sumCMC, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/c/Commandeer.java b/Mage.Sets/src/mage/cards/c/Commandeer.java index bbb659b2df6..b22d5e57bf2 100644 --- a/Mage.Sets/src/mage/cards/c/Commandeer.java +++ b/Mage.Sets/src/mage/cards/c/Commandeer.java @@ -76,7 +76,7 @@ class CommandeerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (controller != null && spell != null) { spell.setControllerId(controller.getId()); spell.chooseNewTargets(game, controller.getId(), false, false, null); diff --git a/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java b/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java new file mode 100644 index 00000000000..c9068c50703 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java @@ -0,0 +1,60 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.game.permanent.token.JunkToken; +import mage.target.TargetPermanent; + +/** + * @author Cguy7777 + */ +public final class CommanderSofiaDaguerre extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("legendary permanent"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + } + + public CommanderSofiaDaguerre(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Crash Landing -- When Commander Sofia Daguerre enters the battlefield, + // destroy up to one target legendary permanent. That permanent's controller creates a Junk token. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new JunkToken()) + .setText("that permanent's controller creates a Junk token")); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability.withFlavorWord("Crash Landing")); + } + + private CommanderSofiaDaguerre(final CommanderSofiaDaguerre card) { + super(card); + } + + @Override + public CommanderSofiaDaguerre copy() { + return new CommanderSofiaDaguerre(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CommandersPlate.java b/Mage.Sets/src/mage/cards/c/CommandersPlate.java index 7fe92ff32fd..90fc3e39ac1 100644 --- a/Mage.Sets/src/mage/cards/c/CommandersPlate.java +++ b/Mage.Sets/src/mage/cards/c/CommandersPlate.java @@ -86,15 +86,17 @@ class CommandersPlateEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { - super.init(source, game); if (!affectedObjectsSet) { return; } Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (equipment == null || equipment.getAttachedTo() == null) { + discard(); return; } this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game)); + + super.init(source, game); // must call at the end due target pointer setup } @Override @@ -105,7 +107,7 @@ class CommandersPlateEffect extends ContinuousEffectImpl { } Permanent permanent = null; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage.Sets/src/mage/cards/c/CompoundFracture.java b/Mage.Sets/src/mage/cards/c/CompoundFracture.java new file mode 100644 index 00000000000..d74a786ae19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CompoundFracture.java @@ -0,0 +1,52 @@ +package mage.cards.c; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author tiera3 - modified from Growth Cycle + */ +public final class CompoundFracture extends CardImpl { + + private static final FilterCard filter = new FilterCard("card named Compound Fracture"); + static { + filter.add(new NamePredicate("Compound Fracture")); + } + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter, -1); + private static final String rule = "It gets an additional -1/-1 until end of turn for each " + xValue.getMessage(); + private static final Hint hint = new ValueHint( + "Cards named Compound Fracture in your graveyard", new CardsInControllerGraveyardCount(filter) + ); + + public CompoundFracture(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}"); + + // Target creature gets -1/-1 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(-1, -1, Duration.EndOfTurn)); + // It gets an additional -1/-1 until end of turn for each card named Compound Fracture in your graveyard. + this.getSpellAbility().addEffect(new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn).setText(rule)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(hint); + } + + private CompoundFracture(final CompoundFracture card) { + super(card); + } + + @Override + public CompoundFracture copy() { + return new CompoundFracture(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConfoundingConundrum.java b/Mage.Sets/src/mage/cards/c/ConfoundingConundrum.java index ced9f3a7ff4..dea670eae21 100644 --- a/Mage.Sets/src/mage/cards/c/ConfoundingConundrum.java +++ b/Mage.Sets/src/mage/cards/c/ConfoundingConundrum.java @@ -100,7 +100,7 @@ class ConfoundingConundrumEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/ConnectingTheDots.java b/Mage.Sets/src/mage/cards/c/ConnectingTheDots.java new file mode 100644 index 00000000000..0e634309abd --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConnectingTheDots.java @@ -0,0 +1,48 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardHandCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +/** + * @author Cguy7777 + */ +public final class ConnectingTheDots extends CardImpl { + + public ConnectingTheDots(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + // Whenever a creature you control attacks, exile the top card of your library face down. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility( + new ExileCardsFromTopOfLibraryControllerEffect(1, true, true, true))); + + // {1}{R}, Discard your hand, Sacrifice Connecting the Dots: Put all cards exiled with Connecting the Dots into their owners' hands. + Ability ability = new SimpleActivatedAbility( + new ReturnFromExileForSourceEffect(Zone.HAND) + .withText(true, true, true), + new ManaCostsImpl<>("{1}{R}")); + ability.addCost(new DiscardHandCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private ConnectingTheDots(final ConnectingTheDots card) { + super(card); + } + + @Override + public ConnectingTheDots copy() { + return new ConnectingTheDots(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConquerorsGalleon.java b/Mage.Sets/src/mage/cards/c/ConquerorsGalleon.java index 5485cd0b8a1..49c161d5b9b 100644 --- a/Mage.Sets/src/mage/cards/c/ConquerorsGalleon.java +++ b/Mage.Sets/src/mage/cards/c/ConquerorsGalleon.java @@ -37,7 +37,7 @@ public final class ConquerorsGalleon extends CardImpl { new ExileAndReturnSourceEffect( PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.IT, true ) - )), false, "When {this} attacks, exile it at the end of combat, " + + )), false, "When {this} attacks, exile it at end of combat, " + "then return it to the battlefield transformed under your control." )); diff --git a/Mage.Sets/src/mage/cards/c/ContainmentConstruct.java b/Mage.Sets/src/mage/cards/c/ContainmentConstruct.java index fd6889ef895..da590c1c4b0 100644 --- a/Mage.Sets/src/mage/cards/c/ContainmentConstruct.java +++ b/Mage.Sets/src/mage/cards/c/ContainmentConstruct.java @@ -98,7 +98,7 @@ class ContainmentConstructEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Card discardedCard = game.getCard(targetPointer.getFirst(game, source)); + Card discardedCard = game.getCard(getTargetPointer().getFirst(game, source)); Card containmentConstruct = game.getCard(source.getSourceId()); if (discardedCard != null && containmentConstruct != null) { diff --git a/Mage.Sets/src/mage/cards/c/ConvincingMirage.java b/Mage.Sets/src/mage/cards/c/ConvincingMirage.java index f9b9a6b0f5b..c496bf77be8 100644 --- a/Mage.Sets/src/mage/cards/c/ConvincingMirage.java +++ b/Mage.Sets/src/mage/cards/c/ConvincingMirage.java @@ -71,6 +71,11 @@ class ConvincingMirageContinousEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + discard(); + return; + } + switch (choice) { case FOREST: dependencyTypes.add(DependencyType.BecomeForest); diff --git a/Mage.Sets/src/mage/cards/c/CopperCarapace.java b/Mage.Sets/src/mage/cards/c/CopperCarapace.java index 8cca2d1e95b..bef0619b230 100644 --- a/Mage.Sets/src/mage/cards/c/CopperCarapace.java +++ b/Mage.Sets/src/mage/cards/c/CopperCarapace.java @@ -1,7 +1,8 @@ - package mage.cards.c; import java.util.UUID; + +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.combat.CantBlockAttachedEffect; @@ -13,7 +14,6 @@ import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; /** * @@ -26,9 +26,11 @@ public final class CopperCarapace extends CardImpl { this.subtype.add(SubType.EQUIPMENT); this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(3))); + // Equipped creature gets +2/+2 and can't block. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(2, 2))); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBlockAttachedEffect(AttachmentType.EQUIPMENT))); + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new CantBlockAttachedEffect(AttachmentType.EQUIPMENT).setText("and can't block")); + this.addAbility(ability); } private CopperCarapace(final CopperCarapace card) { diff --git a/Mage.Sets/src/mage/cards/c/CopyCatchers.java b/Mage.Sets/src/mage/cards/c/CopyCatchers.java new file mode 100644 index 00000000000..81025973da4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CopyCatchers.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.SurveilTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.CreateTokenCopySourceEffect; +import mage.abilities.effects.common.DoIfCostPaid; +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 PurpleCrowbar + */ +public final class CopyCatchers extends CardImpl { + + public CopyCatchers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + this.subtype.add(SubType.FAERIE); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you surveil, you may pay {1}{U}. If you do, create a token that’s a copy of Copy Catchers. + this.addAbility(new SurveilTriggeredAbility(new DoIfCostPaid( + new CreateTokenCopySourceEffect(), new ManaCostsImpl<>("{1}{U}") + ))); + } + + private CopyCatchers(final CopyCatchers card) { + super(card); + } + + @Override + public CopyCatchers copy() { + return new CopyCatchers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CoralFighters.java b/Mage.Sets/src/mage/cards/c/CoralFighters.java index b1c4e107bb0..f4e3713a23d 100644 --- a/Mage.Sets/src/mage/cards/c/CoralFighters.java +++ b/Mage.Sets/src/mage/cards/c/CoralFighters.java @@ -60,7 +60,7 @@ class CoralFightersEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player defendingPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player defendingPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if(controller != null && defendingPlayer != null) { Card card = defendingPlayer.getLibrary().getFromTop(game); if(card != null) { diff --git a/Mage.Sets/src/mage/cards/c/CorpseDance.java b/Mage.Sets/src/mage/cards/c/CorpseDance.java index d7f130ef6f9..6a62a0c8fe5 100644 --- a/Mage.Sets/src/mage/cards/c/CorpseDance.java +++ b/Mage.Sets/src/mage/cards/c/CorpseDance.java @@ -79,14 +79,14 @@ class CorpseDanceEffect extends OneShotEffect { if (controller.moveCards(lastCreatureCard, Zone.BATTLEFIELD, source, game)) { Permanent creature = game.getPermanent(lastCreatureCard.getId()); if (creature != null) { - FixedTarget fixedTarget = new FixedTarget(creature, game); + FixedTarget blueprintTarget = new FixedTarget(creature, game); // Gains Haste ContinuousEffect hasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - hasteEffect.setTargetPointer(fixedTarget); + hasteEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(hasteEffect, source); // Exile it at end of turn ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD); - exileEffect.setTargetPointer(fixedTarget); + exileEffect.setTargetPointer(blueprintTarget.copy()); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect); game.addDelayedTriggeredAbility(delayedAbility, source); } diff --git a/Mage.Sets/src/mage/cards/c/CorruptedResolve.java b/Mage.Sets/src/mage/cards/c/CorruptedResolve.java index c794893a4f9..b9e8ef5f6ce 100644 --- a/Mage.Sets/src/mage/cards/c/CorruptedResolve.java +++ b/Mage.Sets/src/mage/cards/c/CorruptedResolve.java @@ -51,11 +51,11 @@ class CorruptedResolveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); if (player != null && player.getCounters().containsKey(CounterType.POISON)) - return game.getStack().counter(targetPointer.getFirst(game, source), source, game); + return game.getStack().counter(getTargetPointer().getFirst(game, source), source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/c/CosimaGodOfTheVoyage.java b/Mage.Sets/src/mage/cards/c/CosimaGodOfTheVoyage.java index e667d481c41..2b996ed7197 100644 --- a/Mage.Sets/src/mage/cards/c/CosimaGodOfTheVoyage.java +++ b/Mage.Sets/src/mage/cards/c/CosimaGodOfTheVoyage.java @@ -258,7 +258,7 @@ class TheOmenkeelEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); int damage = (Integer) getValue("damage"); if (player == null || damage < 1) { return false; diff --git a/Mage.Sets/src/mage/cards/c/CosmiumCatalyst.java b/Mage.Sets/src/mage/cards/c/CosmiumCatalyst.java index addd304c3f2..292bc3b8620 100644 --- a/Mage.Sets/src/mage/cards/c/CosmiumCatalyst.java +++ b/Mage.Sets/src/mage/cards/c/CosmiumCatalyst.java @@ -88,7 +88,7 @@ class CosmiumCatalystEffect extends OneShotEffect { if (!target.canChoose(controller.getId(), source, game)) { return true; } - controller.chooseTarget(outcome, target, source, game); + target.chooseTarget(outcome, controller.getId(), source, game); Card chosenCard = game.getCard(target.getFirstTarget()); if (chosenCard != null) { CardUtil.castSpellWithAttributesForFree(controller, source, game, chosenCard); diff --git a/Mage.Sets/src/mage/cards/c/CosmiumConfluence.java b/Mage.Sets/src/mage/cards/c/CosmiumConfluence.java index 6222e1ca1fc..bc287fb4f98 100644 --- a/Mage.Sets/src/mage/cards/c/CosmiumConfluence.java +++ b/Mage.Sets/src/mage/cards/c/CosmiumConfluence.java @@ -104,16 +104,16 @@ class CosmiumConfluenceEffect extends OneShotEffect { } controller.choose(outcome, target, source, game); - FixedTarget fixedTarget = new FixedTarget(target.getFirstTarget(), game); + FixedTarget blueprintTarget = new FixedTarget(target.getFirstTarget(), game); new AddCountersTargetEffect(CounterType.P1P1.createInstance(3)) - .setTargetPointer(fixedTarget) + .setTargetPointer(blueprintTarget.copy()) .apply(game, source); ContinuousEffect effect = new BecomesCreatureTargetEffect( new CreatureToken(0, 0, "0/0 Elemental creature with haste", SubType.ELEMENTAL) .withAbility(HasteAbility.getInstance()), false, true, Duration.Custom ); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/c/Counterbalance.java b/Mage.Sets/src/mage/cards/c/Counterbalance.java index 7182ae8b17a..6f103acf7de 100644 --- a/Mage.Sets/src/mage/cards/c/Counterbalance.java +++ b/Mage.Sets/src/mage/cards/c/Counterbalance.java @@ -64,7 +64,7 @@ class CounterbalanceEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (controller != null && sourcePermanent != null) { - Spell spell = (Spell) game.getStack().getStackObject(targetPointer.getFirst(game, source)); + Spell spell = (Spell) game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Card topcard = controller.getLibrary().getFromTop(game); if (topcard != null) { diff --git a/Mage.Sets/src/mage/cards/c/Countermand.java b/Mage.Sets/src/mage/cards/c/Countermand.java index 7dd02bf6ed5..bbb86532444 100644 --- a/Mage.Sets/src/mage/cards/c/Countermand.java +++ b/Mage.Sets/src/mage/cards/c/Countermand.java @@ -58,7 +58,7 @@ class CountermandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean countered = false; - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (game.getStack().counter(source.getFirstTarget(), source, game)) { countered = true; } diff --git a/Mage.Sets/src/mage/cards/c/Counterpoint.java b/Mage.Sets/src/mage/cards/c/Counterpoint.java new file mode 100644 index 00000000000..6b10a70986f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Counterpoint.java @@ -0,0 +1,89 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSpell; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Counterpoint extends CardImpl { + + public Counterpoint(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}{B}"); + + // Counter target spell. You may cast a creature, instant, sorcery, or planeswalker spell from your graveyard with mana value less than or equal to that spell's mana value without paying its mana cost. + this.getSpellAbility().addEffect(new CounterpointEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + private Counterpoint(final Counterpoint card) { + super(card); + } + + @Override + public Counterpoint copy() { + return new Counterpoint(this); + } +} + +class CounterpointEffect extends OneShotEffect { + + private static final FilterCard baseFilter = new FilterCard(); + + static { + baseFilter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate(), + CardType.PLANESWALKER.getPredicate() + )); + } + + CounterpointEffect() { + super(Outcome.Benefit); + staticText = "counter target spell. You may cast a creature, instant, sorcery, or planeswalker spell from " + + "your graveyard with mana value less than or equal to that spell's mana value without paying its mana cost"; + } + + private CounterpointEffect(final CounterpointEffect effect) { + super(effect); + } + + @Override + public CounterpointEffect copy() { + return new CounterpointEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (player == null || spell == null) { + return false; + } + int mv = spell.getManaValue(); + game.getStack().counter(spell.getId(), source, game); + Cards cards = new CardsImpl(player.getGraveyard()); + FilterCard filter = baseFilter.copy(); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, mv + 1)); + CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CovetedJewel.java b/Mage.Sets/src/mage/cards/c/CovetedJewel.java index 69c0f4f7b7f..13fb573dc71 100644 --- a/Mage.Sets/src/mage/cards/c/CovetedJewel.java +++ b/Mage.Sets/src/mage/cards/c/CovetedJewel.java @@ -1,18 +1,18 @@ package mage.cards.c; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.effects.mana.AddManaOfAnyColorEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -59,7 +59,7 @@ class CovetedJewelTriggeredAbility extends TriggeredAbilityImpl { public CovetedJewelTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardTargetEffect(3), false); - this.addEffect(new CovetedJewelControlEffect()); + this.addEffect(new TargetPlayerGainControlSourceEffect()); this.addEffect(new UntapSourceEffect()); } @@ -103,31 +103,3 @@ class CovetedJewelTriggeredAbility extends TriggeredAbilityImpl { + "and gains control of {this}. Untap it."; } } - -class CovetedJewelControlEffect extends ContinuousEffectImpl { - - CovetedJewelControlEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - } - - private CovetedJewelControlEffect(final CovetedJewelControlEffect effect) { - super(effect); - } - - @Override - public CovetedJewelControlEffect copy() { - return new CovetedJewelControlEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - Player newControllingPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (permanent == null || newControllingPlayer == null || !newControllingPlayer.canRespond()) { - this.discard(); - return false; - } - permanent.changeControllerId(getTargetPointer().getFirst(game, source), game, source); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/c/CowardKiller.java b/Mage.Sets/src/mage/cards/c/CowardKiller.java index e7717532831..fdd1cfd0467 100644 --- a/Mage.Sets/src/mage/cards/c/CowardKiller.java +++ b/Mage.Sets/src/mage/cards/c/CowardKiller.java @@ -59,7 +59,7 @@ class KillerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null) { target.damage(3, source, game); for (Permanent p : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game)) { diff --git a/Mage.Sets/src/mage/cards/c/CragSaurian.java b/Mage.Sets/src/mage/cards/c/CragSaurian.java index f523ecc8747..fdac30c0a13 100644 --- a/Mage.Sets/src/mage/cards/c/CragSaurian.java +++ b/Mage.Sets/src/mage/cards/c/CragSaurian.java @@ -1,21 +1,14 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SourceDealsDamageToThisTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** * @@ -30,7 +23,7 @@ public final class CragSaurian extends CardImpl { this.toughness = new MageInt(4); // Whenever a source deals damage to Crag Saurian, that source's controller gains control of Crag Saurian. - this.addAbility(new SourceDealsDamageToThisTriggeredAbility(new CragSaurianEffect())); + this.addAbility(new SourceDealsDamageToThisTriggeredAbility(new TargetPlayerGainControlSourceEffect("that source's controller"))); } private CragSaurian(final CragSaurian card) { @@ -42,33 +35,3 @@ public final class CragSaurian extends CardImpl { return new CragSaurian(this); } } - -class CragSaurianEffect extends OneShotEffect { - - CragSaurianEffect() { - super(Outcome.GainControl); - this.staticText = "that source's controller gains control of {this}"; - } - - private CragSaurianEffect(final CragSaurianEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Player newController = game.getPlayer(this.getTargetPointer().getFirst(game, source)); - if (newController != null && controller != null && !controller.equals(newController)) { - ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, newController.getId()); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), game)); - game.addEffect(effect, source); - return true; - } - return false; - } - - @Override - public CragSaurianEffect copy() { - return new CragSaurianEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java b/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java index 488c1d69b57..748ce6d7217 100644 --- a/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java +++ b/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java @@ -9,12 +9,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TimingRule; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.target.targetpointer.SecondTargetPointer; /** @@ -23,8 +22,8 @@ import mage.target.targetpointer.SecondTargetPointer; */ public final class CrawlFromTheCellar extends CardImpl { - private static final FilterControlledCreaturePermanent filter - = new FilterControlledCreaturePermanent(SubType.ZOMBIE); + private static final FilterControlledPermanent filter + = new FilterControlledPermanent(SubType.ZOMBIE); public CrawlFromTheCellar(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); @@ -34,7 +33,7 @@ public final class CrawlFromTheCellar extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setTargetPointer(new SecondTargetPointer())); - this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1, filter, false)); + this.getSpellAbility().addTarget(new TargetControlledPermanent(0, 1, filter, false)); // Flashback {3}{B} this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{B}"))); diff --git a/Mage.Sets/src/mage/cards/c/CrimsonCaravaneer.java b/Mage.Sets/src/mage/cards/c/CrimsonCaravaneer.java new file mode 100644 index 00000000000..db46ef43b53 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrimsonCaravaneer.java @@ -0,0 +1,47 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.JunkToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CrimsonCaravaneer extends CardImpl { + + public CrimsonCaravaneer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Crimson Caravaneer deals combat damage to a player, create a Junk token. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new CreateTokenEffect(new JunkToken()), false)); + } + + private CrimsonCaravaneer(final CrimsonCaravaneer card) { + super(card); + } + + @Override + public CrimsonCaravaneer copy() { + return new CrimsonCaravaneer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrookclawElder.java b/Mage.Sets/src/mage/cards/c/CrookclawElder.java index a1a954a50ab..8e2b5d4a5ce 100644 --- a/Mage.Sets/src/mage/cards/c/CrookclawElder.java +++ b/Mage.Sets/src/mage/cards/c/CrookclawElder.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.MageInt; @@ -13,10 +12,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.Predicate; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -26,15 +24,14 @@ import java.util.UUID; */ public final class CrookclawElder extends CardImpl { - private static final FilterControlledCreaturePermanent filter - = new FilterControlledCreaturePermanent(SubType.BIRD, "untapped Birds you control"); - private static final FilterControlledCreaturePermanent filter2 - = new FilterControlledCreaturePermanent(SubType.WIZARD, "untapped Wizards you control"); - private static final Predicate pred = TappedPredicate.UNTAPPED; + private static final FilterControlledPermanent filter + = new FilterControlledPermanent(SubType.BIRD, "untapped Birds you control"); + private static final FilterControlledPermanent filter2 + = new FilterControlledPermanent(SubType.WIZARD, "untapped Wizards you control"); static { - filter.add(pred); - filter2.add(pred); + filter.add(TappedPredicate.UNTAPPED); + filter2.add(TappedPredicate.UNTAPPED); } public CrookclawElder(UUID ownerId, CardSetInfo setInfo) { @@ -51,7 +48,7 @@ public final class CrookclawElder extends CardImpl { // Tap two untapped Birds you control: Draw a card. Ability ability = new SimpleActivatedAbility( new DrawCardSourceControllerEffect(1), - new TapTargetCost(new TargetControlledCreaturePermanent( + new TapTargetCost(new TargetControlledPermanent( 2, 2, filter, true )) ); @@ -60,7 +57,7 @@ public final class CrookclawElder extends CardImpl { // Tap two untapped Wizards you control: Target creature gains flying until end of turn. ability = new SimpleActivatedAbility( new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), - new TapTargetCost(new TargetControlledCreaturePermanent( + new TapTargetCost(new TargetControlledPermanent( 2, 2, filter2, true )) ); diff --git a/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java b/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java index e9cfe4ae7e4..08e928f2c3a 100644 --- a/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java +++ b/Mage.Sets/src/mage/cards/c/CrownOfEmpires.java @@ -57,7 +57,7 @@ class CrownOfEmpiresEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); boolean scepter = false; boolean throne = false; for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { @@ -103,7 +103,7 @@ class CrownOfEmpiresControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); UUID controllerId = (UUID) game.getState().getValue(source.getSourceId().toString()); if (permanent != null && controllerId != null) { return permanent.changeControllerId(controllerId, game, source); diff --git a/Mage.Sets/src/mage/cards/c/CruelReality.java b/Mage.Sets/src/mage/cards/c/CruelReality.java index 9a7fa48fb40..3470bcf2aea 100644 --- a/Mage.Sets/src/mage/cards/c/CruelReality.java +++ b/Mage.Sets/src/mage/cards/c/CruelReality.java @@ -80,7 +80,7 @@ class CruelRealityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player cursedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player cursedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (cursedPlayer == null || controller == null) { return false; diff --git a/Mage.Sets/src/mage/cards/c/CrushUnderfoot.java b/Mage.Sets/src/mage/cards/c/CrushUnderfoot.java index fc145789f52..37462e97d9b 100644 --- a/Mage.Sets/src/mage/cards/c/CrushUnderfoot.java +++ b/Mage.Sets/src/mage/cards/c/CrushUnderfoot.java @@ -47,7 +47,7 @@ public final class CrushUnderfoot extends CardImpl { class CrushUnderfootEffect extends OneShotEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Giant you control"); + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Giant creature you control"); static { filter.add(SubType.GIANT.getPredicate()); } diff --git a/Mage.Sets/src/mage/cards/c/Cryptbreaker.java b/Mage.Sets/src/mage/cards/c/Cryptbreaker.java index 528a3d6923f..999f89f8197 100644 --- a/Mage.Sets/src/mage/cards/c/Cryptbreaker.java +++ b/Mage.Sets/src/mage/cards/c/Cryptbreaker.java @@ -16,10 +16,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.permanent.token.ZombieToken; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -28,7 +28,7 @@ import java.util.UUID; */ public final class Cryptbreaker extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Zombies you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Zombies you control"); static { filter.add(TappedPredicate.UNTAPPED); @@ -49,7 +49,7 @@ public final class Cryptbreaker extends CardImpl { // Tap three untapped Zombies you control: You draw a card and you lose 1 life. Effect effect = new DrawCardSourceControllerEffect(1, "you"); - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapTargetCost(new TargetControlledCreaturePermanent(3, 3, filter, true))); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapTargetCost(new TargetControlledPermanent(3, 3, filter, true))); effect = new LoseLifeSourceControllerEffect(1); ability.addEffect(effect.concatBy("and")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/Cryptex.java b/Mage.Sets/src/mage/cards/c/Cryptex.java new file mode 100644 index 00000000000..0972dbec9cd --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Cryptex.java @@ -0,0 +1,49 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class Cryptex extends CardImpl { + + public Cryptex(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // {T}, Collect evidence 3: Add one mana of any color. Put an unlock counter on Cryptex. + Ability ability = new AnyColorManaAbility(); + ability.addCost(new CollectEvidenceCost(3)); + ability.addEffect(new AddCountersSourceEffect(CounterType.UNLOCK.createInstance())); + this.addAbility(ability); + + // Sacrifice Cryptex: Surveil 3, then draw three cards. Activate only if Cryptex has five or more unlock counters on it. + Ability sacAbility = new ConditionalActivatedAbility(new SurveilEffect(3, false), new SacrificeSourceCost(), + new SourceHasCounterCondition(CounterType.UNLOCK, 5)); + sacAbility.addEffect(new DrawCardSourceControllerEffect(3).concatBy(", then")); + this.addAbility(sacAbility); + + } + + private Cryptex(final Cryptex card) { + super(card); + } + + @Override + public Cryptex copy() { + return new Cryptex(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrystalGrotto.java b/Mage.Sets/src/mage/cards/c/CrystalGrotto.java index 643dde55e58..a11715d3416 100644 --- a/Mage.Sets/src/mage/cards/c/CrystalGrotto.java +++ b/Mage.Sets/src/mage/cards/c/CrystalGrotto.java @@ -22,7 +22,7 @@ public final class CrystalGrotto extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // When Crystal Grotto enters the battlefield, scry 1. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1, false))); // {T}: Add {C}. this.addAbility(new ColorlessManaAbility()); diff --git a/Mage.Sets/src/mage/cards/c/Cultivate.java b/Mage.Sets/src/mage/cards/c/Cultivate.java index 8f54a7e5298..db5b9a96978 100644 --- a/Mage.Sets/src/mage/cards/c/Cultivate.java +++ b/Mage.Sets/src/mage/cards/c/Cultivate.java @@ -1,20 +1,11 @@ package mage.cards.c; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; import mage.cards.*; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; -import java.util.Set; import java.util.UUID; /** @@ -25,8 +16,9 @@ public final class Cultivate extends CardImpl { public Cultivate(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); - // Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new CultivateEffect()); + // Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect( + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS))); } @@ -40,63 +32,3 @@ public final class Cultivate extends CardImpl { } } - -class CultivateEffect extends OneShotEffect { - - protected static final FilterCard filter = new FilterCard("card to put on the battlefield tapped"); - - CultivateEffect() { - super(Outcome.PutLandInPlay); - staticText = "search your library for up to two basic land cards, reveal those cards, " + - "put one onto the battlefield tapped and the other into your hand, then shuffle"; - } - - private CultivateEffect(final CultivateEffect effect) { - super(effect); - } - - @Override - public CultivateEffect copy() { - return new CultivateEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); - if (controller == null || sourceObject == null) { - return false; - } - TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, source, game)) { - if (!target.getTargets().isEmpty()) { - Cards revealed = new CardsImpl(target.getTargets()); - controller.revealCards(sourceObject.getIdName(), revealed, game); - if (target.getTargets().size() == 2) { - TargetCard target2 = new TargetCard(Zone.LIBRARY, filter); - controller.choose(Outcome.Benefit, revealed, target2, source, game); - Card card = revealed.get(target2.getFirstTarget(), game); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - revealed.remove(card); - } - Set cards = revealed.getCards(game); - card = cards.isEmpty() ? null : cards.iterator().next(); - if (card != null) { - controller.moveCards(card, Zone.HAND, source, game); - } - } else if (target.getTargets().size() == 1) { - Set cards = revealed.getCards(game); - Card card = cards.isEmpty() ? null : cards.iterator().next(); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - } - } - } - } - controller.shuffleLibrary(source, game); - return true; - - } - -} diff --git a/Mage.Sets/src/mage/cards/c/CulturalExchange.java b/Mage.Sets/src/mage/cards/c/CulturalExchange.java index 648df0fad48..8e6820e0200 100644 --- a/Mage.Sets/src/mage/cards/c/CulturalExchange.java +++ b/Mage.Sets/src/mage/cards/c/CulturalExchange.java @@ -63,8 +63,8 @@ class CulturalExchangeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player1 = game.getPlayer(targetPointer.getTargets(game, source).get(0)); - Player player2 = game.getPlayer(targetPointer.getTargets(game, source).get(1)); + Player player1 = game.getPlayer(getTargetPointer().getTargets(game, source).get(0)); + Player player2 = game.getPlayer(getTargetPointer().getTargets(game, source).get(1)); Player controller = game.getPlayer(source.getControllerId()); if (player1 == null || player2 == null || controller == null) { return false; diff --git a/Mage.Sets/src/mage/cards/c/CunningAbduction.java b/Mage.Sets/src/mage/cards/c/CunningAbduction.java index 78db0ec6321..2923881982f 100644 --- a/Mage.Sets/src/mage/cards/c/CunningAbduction.java +++ b/Mage.Sets/src/mage/cards/c/CunningAbduction.java @@ -65,7 +65,7 @@ class CunningAbductionExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (opponent != null && sourceObject != null) { opponent.revealCards(sourceObject.getName(), opponent.getHand(), game); diff --git a/Mage.Sets/src/mage/cards/c/CuriousCadaver.java b/Mage.Sets/src/mage/cards/c/CuriousCadaver.java index 0c58f56c104..b0c8e91bd1c 100644 --- a/Mage.Sets/src/mage/cards/c/CuriousCadaver.java +++ b/Mage.Sets/src/mage/cards/c/CuriousCadaver.java @@ -7,8 +7,7 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,8 +16,6 @@ import java.util.UUID; */ public final class CuriousCadaver extends CardImpl { - private static final FilterPermanent filter = new FilterControlledPermanent(SubType.CLUE, "a Clue"); - public CuriousCadaver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); @@ -32,7 +29,7 @@ public final class CuriousCadaver extends CardImpl { // When you sacrifice a Clue, return Curious Cadaver from your graveyard to your hand. this.addAbility(new SacrificePermanentTriggeredAbility( - Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), filter, + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), StaticFilters.FILTER_CONTROLLED_CLUE, TargetController.YOU, SetTargetPointer.NONE, false ).setTriggerPhrase("When you sacrifice a Clue, ")); } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfShallowGraves.java b/Mage.Sets/src/mage/cards/c/CurseOfShallowGraves.java index 9cd8e282558..bcc06572abd 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfShallowGraves.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfShallowGraves.java @@ -16,7 +16,6 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.game.permanent.token.ZombieToken; import mage.players.Player; @@ -120,7 +119,7 @@ class CurseOfShallowEffect extends OneShotEffect { Player attacker = game.getPlayer(this.getTargetPointer().getFirst(game, source)); if (attacker != null && attacker.chooseUse(outcome, "create a tapped 2/2 black Zombie creature token?", source, game)) { Effect effect = new CreateTokenTargetEffect(new ZombieToken(), StaticValue.get(1), true, false); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } return false; diff --git a/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java b/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java index 64e99750d21..3f69fae7843 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java @@ -74,7 +74,7 @@ class CurseOfThePiercedHeartEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java b/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java index 007341d08ef..a9e2e82d4ed 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java @@ -100,7 +100,7 @@ class CurseOfTheWerefoxFightEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent triggeredCreature = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent triggeredCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); Permanent target = game.getPermanent(source.getFirstTarget()); if (triggeredCreature != null && target != null diff --git a/Mage.Sets/src/mage/cards/c/CursedScroll.java b/Mage.Sets/src/mage/cards/c/CursedScroll.java index ac858f68b09..3d3f1fb38c5 100644 --- a/Mage.Sets/src/mage/cards/c/CursedScroll.java +++ b/Mage.Sets/src/mage/cards/c/CursedScroll.java @@ -71,12 +71,12 @@ class CursedScrollEffect extends OneShotEffect { revealed.add(card); controller.revealCards(sourceObject.getIdName(), revealed, game); if (CardUtil.haveSameNames(card, cardName, game)) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature != null) { creature.damage(2, source.getSourceId(), source, game, false, true); return true; } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(2, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/c/CustodiLich.java b/Mage.Sets/src/mage/cards/c/CustodiLich.java index fe7a3b09173..60d5eae371c 100644 --- a/Mage.Sets/src/mage/cards/c/CustodiLich.java +++ b/Mage.Sets/src/mage/cards/c/CustodiLich.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.MageInt; @@ -12,7 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.TargetPlayer; import java.util.UUID; @@ -35,7 +34,7 @@ public final class CustodiLich extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new BecomesMonarchSourceEffect()).addHint(MonarchHint.instance)); // Whenever you become the monarch, target player sacrifices a creature. - Ability ability = new BecomesMonarchSourceControllerTriggeredAbility(new SacrificeEffect(new FilterControlledCreaturePermanent("creature"), 1, "target player")); + Ability ability = new BecomesMonarchSourceControllerTriggeredAbility(new SacrificeEffect(StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target player")); ability.addTarget(new TargetPlayer()); this.addAbility(ability); @@ -49,4 +48,4 @@ public final class CustodiLich extends CardImpl { public CustodiLich copy() { return new CustodiLich(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index 56ed5e0ec75..86a1f7440d1 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -79,7 +79,7 @@ class BecomeSwampEffect extends BecomesBasicLandTargetEffect { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Permanent land = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent land = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (land == null || land.getCounters(game).getCount(CounterType.MIRE) < 1) { this.discard(); return false; diff --git a/Mage.Sets/src/mage/cards/d/DamnablePact.java b/Mage.Sets/src/mage/cards/d/DamnablePact.java index b5f048dff3f..1706b04ba43 100644 --- a/Mage.Sets/src/mage/cards/d/DamnablePact.java +++ b/Mage.Sets/src/mage/cards/d/DamnablePact.java @@ -50,7 +50,7 @@ class DamnablePactEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.drawCards(source.getManaCostsToPay().getX(), source, game); targetPlayer.loseLife(source.getManaCostsToPay().getX(), game, source, false); diff --git a/Mage.Sets/src/mage/cards/d/DaringSleuth.java b/Mage.Sets/src/mage/cards/d/DaringSleuth.java index 546e0a48ad4..b6a2e57ab33 100644 --- a/Mage.Sets/src/mage/cards/d/DaringSleuth.java +++ b/Mage.Sets/src/mage/cards/d/DaringSleuth.java @@ -8,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,8 +17,6 @@ import java.util.UUID; */ public final class DaringSleuth extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "a Clue"); - public DaringSleuth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); this.subtype.add(SubType.HUMAN); @@ -30,7 +28,7 @@ public final class DaringSleuth extends CardImpl { // When you sacrifice a Clue, transform Daring Sleuth. this.addAbility(new TransformAbility()); - this.addAbility(new SacrificePermanentTriggeredAbility(new TransformSourceEffect(), filter) + this.addAbility(new SacrificePermanentTriggeredAbility(new TransformSourceEffect(), StaticFilters.FILTER_CONTROLLED_CLUE) .setTriggerPhrase("When you sacrifice a Clue, ")); } diff --git a/Mage.Sets/src/mage/cards/d/DarkSphere.java b/Mage.Sets/src/mage/cards/d/DarkSphere.java index 2a93aa89600..c90d347b09f 100644 --- a/Mage.Sets/src/mage/cards/d/DarkSphere.java +++ b/Mage.Sets/src/mage/cards/d/DarkSphere.java @@ -70,6 +70,7 @@ class DarkSpherePreventionEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/d/DarkTemper.java b/Mage.Sets/src/mage/cards/d/DarkTemper.java index e6596f69222..0e3e7e66a18 100644 --- a/Mage.Sets/src/mage/cards/d/DarkTemper.java +++ b/Mage.Sets/src/mage/cards/d/DarkTemper.java @@ -58,7 +58,7 @@ class DarkTemperEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java b/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java index 73be6c0a396..9302468a0ec 100644 --- a/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java +++ b/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java @@ -81,8 +81,8 @@ class DarthTyranusEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player1 = game.getPlayer(targetPointer.getTargets(game, source).get(0)); - Player player2 = game.getPlayer(targetPointer.getTargets(game, source).get(1)); + Player player1 = game.getPlayer(getTargetPointer().getTargets(game, source).get(0)); + Player player2 = game.getPlayer(getTargetPointer().getTargets(game, source).get(1)); if (player1 != null && player2 != null) { player1.setLife(5, game, source); player1.setLife(30, game, source); diff --git a/Mage.Sets/src/mage/cards/d/DavrosDalekCreator.java b/Mage.Sets/src/mage/cards/d/DavrosDalekCreator.java index 419e23c33d7..629bf5f1d89 100644 --- a/Mage.Sets/src/mage/cards/d/DavrosDalekCreator.java +++ b/Mage.Sets/src/mage/cards/d/DavrosDalekCreator.java @@ -43,7 +43,8 @@ public final class DavrosDalekCreator extends CardImpl { Ability ability = new BeginningOfEndStepTriggeredAbility( new ConditionalOneShotEffect( new CreateTokenEffect(new DalekToken()), - new OpponentLostLifeCondition(ComparisonType.OR_GREATER, 3) + new OpponentLostLifeCondition(ComparisonType.OR_GREATER, 3), + "create a 3/3 black Dalek artifact creature token with menace if an opponent lost 3 or more life this turn" ), TargetController.YOU, false ); diff --git a/Mage.Sets/src/mage/cards/d/DazzlingSphinx.java b/Mage.Sets/src/mage/cards/d/DazzlingSphinx.java index 50f72e6b55e..5fe0c65e95d 100644 --- a/Mage.Sets/src/mage/cards/d/DazzlingSphinx.java +++ b/Mage.Sets/src/mage/cards/d/DazzlingSphinx.java @@ -68,7 +68,7 @@ class DazzlingSphinxEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/Deadapult.java b/Mage.Sets/src/mage/cards/d/Deadapult.java index a084eb53a1e..cdeeef86907 100644 --- a/Mage.Sets/src/mage/cards/d/Deadapult.java +++ b/Mage.Sets/src/mage/cards/d/Deadapult.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -12,8 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.target.common.TargetAnyTarget; /** @@ -23,11 +21,7 @@ import mage.target.common.TargetAnyTarget; */ public final class Deadapult extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Zombie"); - - static { - filter.add(SubType.ZOMBIE.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.ZOMBIE, "a Zombie"); public Deadapult(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{R}"); diff --git a/Mage.Sets/src/mage/cards/d/DeathbringerLiege.java b/Mage.Sets/src/mage/cards/d/DeathbringerLiege.java index 7c0ce78279c..44bf071a318 100644 --- a/Mage.Sets/src/mage/cards/d/DeathbringerLiege.java +++ b/Mage.Sets/src/mage/cards/d/DeathbringerLiege.java @@ -82,7 +82,7 @@ class DeathbringerLiegeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent p = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent p = game.getPermanent(getTargetPointer().getFirst(game, source)); if (p != null && p.isTapped()) { p.destroy(source, game, false); } diff --git a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java index 10c13c0086d..496a15fb463 100644 --- a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java +++ b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java @@ -65,7 +65,7 @@ public final class DeathriteShaman extends CardImpl { ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), new ManaCostsImpl<>("{G}")); ability.addCost(new TapSourceCost()); ability.addEffect(new GainLifeEffect(2)); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DeathsporeThallid.java b/Mage.Sets/src/mage/cards/d/DeathsporeThallid.java index 26602153cf5..7598a3d6e63 100644 --- a/Mage.Sets/src/mage/cards/d/DeathsporeThallid.java +++ b/Mage.Sets/src/mage/cards/d/DeathsporeThallid.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -15,9 +14,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -26,10 +24,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class DeathsporeThallid extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Saproling"); - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.SAPROLING, "Saproling"); public DeathsporeThallid(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); diff --git a/Mage.Sets/src/mage/cards/d/DebtOfLoyalty.java b/Mage.Sets/src/mage/cards/d/DebtOfLoyalty.java index b00c1610008..495cfd9c22e 100644 --- a/Mage.Sets/src/mage/cards/d/DebtOfLoyalty.java +++ b/Mage.Sets/src/mage/cards/d/DebtOfLoyalty.java @@ -55,10 +55,10 @@ class DebtOfLoyaltyEffect extends RegenerateTargetEffect { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (super.replaceEvent(event, source, game) && permanent != null) { GainControlTargetEffect effect = new GainControlTargetEffect(Duration.EndOfGame); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/d/DebtorsKnell.java b/Mage.Sets/src/mage/cards/d/DebtorsKnell.java index 3f82460ed42..da8c58450d3 100644 --- a/Mage.Sets/src/mage/cards/d/DebtorsKnell.java +++ b/Mage.Sets/src/mage/cards/d/DebtorsKnell.java @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.TargetController; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -26,7 +27,7 @@ public final class DebtorsKnell extends CardImpl { // ({WB} can be paid with either {W} or {B}.) // At the beginning of your upkeep, put target creature card from a graveyard onto the battlefield under your control. Ability ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), TargetController.YOU, false); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DeceiverOfForm.java b/Mage.Sets/src/mage/cards/d/DeceiverOfForm.java index 6252ebf18b1..056de1ede14 100644 --- a/Mage.Sets/src/mage/cards/d/DeceiverOfForm.java +++ b/Mage.Sets/src/mage/cards/d/DeceiverOfForm.java @@ -81,11 +81,8 @@ class DeceiverOfFormEffect extends OneShotEffect { && ((ModalDoubleFacedCard) cardFromTop).getLeftHalfCard().isCreature(game)) { copyFromCard = ((ModalDoubleFacedCard) cardFromTop).getLeftHalfCard(); } - Permanent newBluePrint = null; - newBluePrint = new PermanentCard(copyFromCard, source.getControllerId(), game); - newBluePrint.assignNewId(); + Permanent newBluePrint = new PermanentCard(copyFromCard, source.getControllerId(), game); CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBluePrint, permanent.getId()); - copyEffect.newId(); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); game.addEffect(copyEffect, newAbility); diff --git a/Mage.Sets/src/mage/cards/d/DecimatorBeetle.java b/Mage.Sets/src/mage/cards/d/DecimatorBeetle.java index 66ce9e10087..29e3b1a0b2e 100644 --- a/Mage.Sets/src/mage/cards/d/DecimatorBeetle.java +++ b/Mage.Sets/src/mage/cards/d/DecimatorBeetle.java @@ -82,7 +82,7 @@ class DecimatorBeetleEffect extends OneShotEffect { if (targetCreature != null && targetCreature.getCounters(game).containsKey(CounterType.M1M1)) { Effect effect = new RemoveCounterTargetEffect(CounterType.M1M1.createInstance(1)); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } targetCreature = game.getPermanent(source.getTargets().get(1).getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/d/DecreeOfSavagery.java b/Mage.Sets/src/mage/cards/d/DecreeOfSavagery.java index a6b79eb4a80..448eb6000a7 100644 --- a/Mage.Sets/src/mage/cards/d/DecreeOfSavagery.java +++ b/Mage.Sets/src/mage/cards/d/DecreeOfSavagery.java @@ -12,11 +12,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; /** - * * @author LoneFox */ @@ -26,7 +25,7 @@ public final class DecreeOfSavagery extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{7}{G}{G}"); // Put four +1/+1 counters on each creature you control. - this.getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(4), new FilterControlledCreaturePermanent())); + this.getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(4), StaticFilters.FILTER_CONTROLLED_CREATURE)); // Cycling {4}{G}{G} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{4}{G}{G}"))); // When you cycle Decree of Savagery, you may put four +1/+1 counters on target creature. diff --git a/Mage.Sets/src/mage/cards/d/DeeprootElite.java b/Mage.Sets/src/mage/cards/d/DeeprootElite.java index e41d034419e..899dcd8bb0f 100644 --- a/Mage.Sets/src/mage/cards/d/DeeprootElite.java +++ b/Mage.Sets/src/mage/cards/d/DeeprootElite.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -13,9 +12,9 @@ import mage.constants.CardType; import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -29,10 +28,7 @@ public final class DeeprootElite extends CardImpl { filterYourAnotherMerfolk.add(TargetController.YOU.getControllerPredicate()); } - private static final FilterControlledCreaturePermanent filterYourAnyMerfolk = new FilterControlledCreaturePermanent(SubType.MERFOLK); - static { - filterYourAnyMerfolk.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterControlledPermanent filterYourAnyMerfolk = new FilterControlledPermanent(SubType.MERFOLK); public DeeprootElite(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); @@ -44,7 +40,7 @@ public final class DeeprootElite extends CardImpl { // Whenever another Merfolk enters the battlefield under your control, put a +1/+1 counter on target Merfolk you control. Ability ability = new EntersBattlefieldControlledTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), filterYourAnotherMerfolk); - ability.addTarget(new TargetControlledCreaturePermanent(filterYourAnyMerfolk)); + ability.addTarget(new TargetControlledPermanent(filterYourAnyMerfolk)); this.addAbility(ability); } @@ -56,4 +52,4 @@ public final class DeeprootElite extends CardImpl { public DeeprootElite copy() { return new DeeprootElite(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DefiantVanguard.java b/Mage.Sets/src/mage/cards/d/DefiantVanguard.java index a1e0f90634b..c6c991bc857 100644 --- a/Mage.Sets/src/mage/cards/d/DefiantVanguard.java +++ b/Mage.Sets/src/mage/cards/d/DefiantVanguard.java @@ -128,7 +128,7 @@ class DefiantVanguardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean result = false; - Permanent blockedCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent blockedCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); Permanent defiantVanguard = game.getPermanent(source.getSourceId()); if (blockedCreature != null) { if (game.getState().getValue(blockedCreature.toString()).equals(blockedCreature.getZoneChangeCounter(game))) { // true if it did not change zones diff --git a/Mage.Sets/src/mage/cards/d/DeflectingPalm.java b/Mage.Sets/src/mage/cards/d/DeflectingPalm.java index dfe9f6a0db2..7189cbe5329 100644 --- a/Mage.Sets/src/mage/cards/d/DeflectingPalm.java +++ b/Mage.Sets/src/mage/cards/d/DeflectingPalm.java @@ -62,8 +62,8 @@ class DeflectingPalmEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/Delay.java b/Mage.Sets/src/mage/cards/d/Delay.java index a127b8e1c96..0e43d711f0f 100644 --- a/Mage.Sets/src/mage/cards/d/Delay.java +++ b/Mage.Sets/src/mage/cards/d/Delay.java @@ -67,7 +67,7 @@ class DelayEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (controller != null && spell != null) { Effect effect = new CounterTargetWithReplacementEffect(PutCards.EXILED); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); Card card = game.getCard(spell.getSourceId()); if (card != null && effect.apply(game, source) && game.getState().getZone(card.getId()) == Zone.EXILED) { boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); diff --git a/Mage.Sets/src/mage/cards/d/DelayTactic.java b/Mage.Sets/src/mage/cards/d/DelayTactic.java index f29d5944d58..85afbee4763 100644 --- a/Mage.Sets/src/mage/cards/d/DelayTactic.java +++ b/Mage.Sets/src/mage/cards/d/DelayTactic.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; @@ -34,8 +34,7 @@ public final class DelayTactic extends CardImpl { // Choose one - // Creatures you control gain hexproof until end of turn. - this.getSpellAbility().addEffect(new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent()) - .setText("Creatures you control gain hexproof until end of turn")); + this.getSpellAbility().addEffect(new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES)); // Creatures target opponent controls don't untap during their next untap step. Mode mode = new Mode(new DelayTacticEffect()); @@ -83,4 +82,4 @@ class DelayTacticEffect extends OneShotEffect { } return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DelifsCone.java b/Mage.Sets/src/mage/cards/d/DelifsCone.java index 346d6f5778c..620156b8529 100644 --- a/Mage.Sets/src/mage/cards/d/DelifsCone.java +++ b/Mage.Sets/src/mage/cards/d/DelifsCone.java @@ -99,7 +99,7 @@ class DelifsConeLifeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (player != null || permanent != null) { player.gainLife(permanent.getPower().getValue(), game, source); return true; diff --git a/Mage.Sets/src/mage/cards/d/DelinaWildMage.java b/Mage.Sets/src/mage/cards/d/DelinaWildMage.java index 4a57069601b..31e0db4035c 100644 --- a/Mage.Sets/src/mage/cards/d/DelinaWildMage.java +++ b/Mage.Sets/src/mage/cards/d/DelinaWildMage.java @@ -84,7 +84,7 @@ class DelinaWildMageEffect extends OneShotEffect { effect.addAdditionalAbilities(new EndOfCombatTriggeredAbility( new ExileSourceEffect(), false, "Exile this creature at end of combat." )); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); while (true) { int result = player.rollDice(outcome, source, game, 20); effect.apply(game, source); diff --git a/Mage.Sets/src/mage/cards/d/DelneyStreetwiseLookout.java b/Mage.Sets/src/mage/cards/d/DelneyStreetwiseLookout.java new file mode 100644 index 00000000000..6cf8e4aa6c7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DelneyStreetwiseLookout.java @@ -0,0 +1,95 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class DelneyStreetwiseLookout extends CardImpl { + + private static final FilterCreaturePermanent filterSmall = new FilterCreaturePermanent("creatures you control with power 2 or less"); + private static final FilterCreaturePermanent filterBig = new FilterCreaturePermanent("creatures with power 3 or greater"); + static { + filterSmall.add(new PowerPredicate(ComparisonType.OR_LESS, 2)); + filterSmall.add(TargetController.YOU.getControllerPredicate()); + filterBig.add(new PowerPredicate(ComparisonType.OR_GREATER, 3)); + } + + public DelneyStreetwiseLookout(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.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Creatures you control with power 2 or less can't be blocked by creatures with power 3 or greater. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByCreaturesAllEffect( + filterSmall, filterBig, Duration.WhileOnBattlefield + ))); + + // If an ability of a creature you control with power 2 or less triggers, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new DelneyStreetwiseLookoutEffect())); + } + + private DelneyStreetwiseLookout(final DelneyStreetwiseLookout card) { + super(card); + } + + @Override + public DelneyStreetwiseLookout copy() { + return new DelneyStreetwiseLookout(this); + } +} + +class DelneyStreetwiseLookoutEffect extends ReplacementEffectImpl { + + DelneyStreetwiseLookoutEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if an ability of a creature you control with power 2 or less triggers, that ability triggers an additional time"; + } + + private DelneyStreetwiseLookoutEffect(final DelneyStreetwiseLookoutEffect effect) { + super(effect); + } + + @Override + public DelneyStreetwiseLookoutEffect copy() { + return new DelneyStreetwiseLookoutEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(event.getSourceId()); + return permanent != null + && permanent.isCreature(game) + && permanent.isControlledBy(source.getControllerId()) + && permanent.getPower().getValue() <= 2; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(event.getAmount() + 1); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DementiaSliver.java b/Mage.Sets/src/mage/cards/d/DementiaSliver.java index 9307702169f..ea88b06ec96 100644 --- a/Mage.Sets/src/mage/cards/d/DementiaSliver.java +++ b/Mage.Sets/src/mage/cards/d/DementiaSliver.java @@ -76,7 +76,7 @@ class DementiaSliverEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); if (opponent != null && sourceObject != null && cardName != null && !cardName.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/d/DemonOfDarkSchemes.java b/Mage.Sets/src/mage/cards/d/DemonOfDarkSchemes.java index d614a125237..975a785f187 100644 --- a/Mage.Sets/src/mage/cards/d/DemonOfDarkSchemes.java +++ b/Mage.Sets/src/mage/cards/d/DemonOfDarkSchemes.java @@ -20,6 +20,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -51,7 +52,7 @@ public final class DemonOfDarkSchemes extends CardImpl { effect.setText("Put target creature card from a graveyard onto the battlefield under your control tapped"); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{2}{B}")); ability.addCost(new PayEnergyCost(4)); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/Dermotaxi.java b/Mage.Sets/src/mage/cards/d/Dermotaxi.java index 88971b76f50..6d058e662b7 100644 --- a/Mage.Sets/src/mage/cards/d/Dermotaxi.java +++ b/Mage.Sets/src/mage/cards/d/Dermotaxi.java @@ -131,7 +131,6 @@ class DermotaxiCopyEffect extends OneShotEffect { DermotaxiCopyApplier applier = new DermotaxiCopyApplier(); applier.apply(game, newBluePrint, source, sourcePermanent.getId()); CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBluePrint, sourcePermanent.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); game.addEffect(copyEffect, source); return true; diff --git a/Mage.Sets/src/mage/cards/d/DescendantsFury.java b/Mage.Sets/src/mage/cards/d/DescendantsFury.java index 108cbf3295c..68fb6ca171e 100644 --- a/Mage.Sets/src/mage/cards/d/DescendantsFury.java +++ b/Mage.Sets/src/mage/cards/d/DescendantsFury.java @@ -75,11 +75,7 @@ class DescendantsFurySacrificeCost extends CostImpl implements SacrificeCost { if (watcher == null) { return false; } - TargetPointer targetPointer = source.getEffects().get(0).getTargetPointer(); - if (targetPointer == null) { - return false; - } - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(source.getEffects().get(0).getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (controller == null || damagedPlayer == null) { return false; @@ -114,11 +110,7 @@ class DescendantsFurySacrificeCost extends CostImpl implements SacrificeCost { if (watcher == null) { return false; } - TargetPointer targetPointer = source.getEffects().get(0).getTargetPointer(); - if (targetPointer == null) { - return false; - } - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(source.getEffects().get(0).getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (controller == null || damagedPlayer == null) { return false; diff --git a/Mage.Sets/src/mage/cards/d/Desertion.java b/Mage.Sets/src/mage/cards/d/Desertion.java index 1e6d40ce236..ab9cb1b0fe6 100644 --- a/Mage.Sets/src/mage/cards/d/Desertion.java +++ b/Mage.Sets/src/mage/cards/d/Desertion.java @@ -55,7 +55,7 @@ class DesertionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DesolateMire.java b/Mage.Sets/src/mage/cards/d/DesolateMire.java new file mode 100644 index 00000000000..7def2806e61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesolateMire.java @@ -0,0 +1,37 @@ +package mage.cards.d; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesolateMire extends CardImpl { + + public DesolateMire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {1}, {T}: Add {W}{B}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new Mana(1, 0, 1, 0, 0, 0, 0, 0), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private DesolateMire(final DesolateMire card) { + super(card); + } + + @Override + public DesolateMire copy() { + return new DesolateMire(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesperateGambit.java b/Mage.Sets/src/mage/cards/d/DesperateGambit.java index 59c3a44d300..1931ccdc0e3 100644 --- a/Mage.Sets/src/mage/cards/d/DesperateGambit.java +++ b/Mage.Sets/src/mage/cards/d/DesperateGambit.java @@ -65,12 +65,16 @@ class DesperateGambitEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.Benefit, source.getControllerId(), source.getSourceId(), source, game); + super.init(source, game); + Player you = game.getPlayer(source.getControllerId()); - if(you != null) { - wonFlip = you.flipCoin(source, game, true); - super.init(source, game); + if (you == null) { + discard(); + return; } + + this.target.choose(Outcome.Benefit, source.getControllerId(), source.getSourceId(), source, game); + this.wonFlip = you.flipCoin(source, game, true); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DetectiveOfTheMonth.java b/Mage.Sets/src/mage/cards/d/DetectiveOfTheMonth.java new file mode 100644 index 00000000000..14b9c3ab535 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DetectiveOfTheMonth.java @@ -0,0 +1,61 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.DrawNthCardTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CitysBlessingCondition; +import mage.abilities.decorator.ConditionalRestrictionEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAllEffect; +import mage.abilities.hint.common.CitysBlessingHint; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.AscendAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.permanent.token.DetectiveToken; + +/** + * @author Cguy7777 + */ +public final class DetectiveOfTheMonth extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.DETECTIVE); + + public DetectiveOfTheMonth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Ascend + this.addAbility(new AscendAbility()); + + // As long as you have the city's blessing, Detectives you control can't be blocked. + Effect effect = new ConditionalRestrictionEffect( + new CantBeBlockedAllEffect(filter, Duration.WhileOnBattlefield), + CitysBlessingCondition.instance, + "as long as you have the city's blessing, Detectives you control can't be blocked"); + this.addAbility(new SimpleStaticAbility(effect).addHint(CitysBlessingHint.instance)); + + // Whenever you draw your second card each turn, create a 2/2 white and blue Detective creature token. + this.addAbility(new DrawNthCardTriggeredAbility( + new CreateTokenEffect(new DetectiveToken()), false, 2)); + } + + private DetectiveOfTheMonth(final DetectiveOfTheMonth card) { + super(card); + } + + @Override + public DetectiveOfTheMonth copy() { + return new DetectiveOfTheMonth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DevourFlesh.java b/Mage.Sets/src/mage/cards/d/DevourFlesh.java index 0489225be6c..750c575f8c7 100644 --- a/Mage.Sets/src/mage/cards/d/DevourFlesh.java +++ b/Mage.Sets/src/mage/cards/d/DevourFlesh.java @@ -59,7 +59,7 @@ class DevourFleshSacrificeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DevouringGreed.java b/Mage.Sets/src/mage/cards/d/DevouringGreed.java index 01d6515607f..b548446b770 100644 --- a/Mage.Sets/src/mage/cards/d/DevouringGreed.java +++ b/Mage.Sets/src/mage/cards/d/DevouringGreed.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -11,11 +10,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetSacrifice; /** @@ -24,17 +22,12 @@ import mage.target.common.TargetSacrifice; */ public final class DevouringGreed extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("any number of Spirits"); - - static { - filter.add(SubType.SPIRIT.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.SPIRIT, "any number of Spirits"); public DevouringGreed(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}{B}"); this.subtype.add(SubType.ARCANE); - // As an additional cost to cast Devouring Greed, you may sacrifice any number of Spirits. this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetSacrifice(0, Integer.MAX_VALUE, filter))); diff --git a/Mage.Sets/src/mage/cards/d/DevouringRage.java b/Mage.Sets/src/mage/cards/d/DevouringRage.java index fff0d4cd2a7..f9b2281f5ce 100644 --- a/Mage.Sets/src/mage/cards/d/DevouringRage.java +++ b/Mage.Sets/src/mage/cards/d/DevouringRage.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -14,10 +13,9 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetSacrifice; import mage.target.targetpointer.FixedTarget; @@ -28,17 +26,12 @@ import mage.target.targetpointer.FixedTarget; */ public final class DevouringRage extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("any number of Spirits"); - - static { - filter.add(SubType.SPIRIT.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.SPIRIT, "any number of Spirits"); public DevouringRage(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{4}{R}"); this.subtype.add(SubType.ARCANE); - // As an additional cost to cast Devouring Rage, you may sacrifice any number of Spirits. this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetSacrifice(0, Integer.MAX_VALUE, filter))); diff --git a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java index 6327d483166..bc8f05ca387 100644 --- a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java +++ b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java @@ -76,7 +76,7 @@ class DiabolicServitudeReturnCreatureEffect extends OneShotEffect { Card cardInGraveyard = game.getCard(getTargetPointer().getFirst(game, source)); if (cardInGraveyard != null) { Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect(); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); game.getState().setValue(source.getSourceId().toString() + "returnedCreature", new MageObjectReference(cardInGraveyard.getId(), game)); diff --git a/Mage.Sets/src/mage/cards/d/DihadaBinderOfWills.java b/Mage.Sets/src/mage/cards/d/DihadaBinderOfWills.java index cdae2a4b16f..9ea7e36b145 100644 --- a/Mage.Sets/src/mage/cards/d/DihadaBinderOfWills.java +++ b/Mage.Sets/src/mage/cards/d/DihadaBinderOfWills.java @@ -1,21 +1,17 @@ package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.CanBeYourCommanderAbility; import mage.abilities.effects.common.RevealLibraryPickControllerEffect; -import mage.abilities.effects.common.UntapAllEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.common.continuous.GainControlAllEffect; -import mage.abilities.keyword.HasteAbility; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; import mage.abilities.keyword.IndestructibleAbility; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.VigilanceAbility; -import mage.cards.Cards; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.Cards; import mage.constants.*; import mage.filter.FilterCard; import mage.filter.StaticFilters; @@ -25,18 +21,20 @@ import mage.game.permanent.token.TreasureToken; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * @author Draya, awjackson */ public final class DihadaBinderOfWills extends CardImpl { - private static final FilterCreaturePermanent legendarycreaturefilter = new FilterCreaturePermanent("legendary creature"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("legendary creature"); static { - legendarycreaturefilter.add(SuperType.LEGENDARY.getPredicate()); + filter.add(SuperType.LEGENDARY.getPredicate()); } -public DihadaBinderOfWills(UUID ownerId, CardSetInfo setInfo) { + public DihadaBinderOfWills(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{R}{W}{B}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.DIHADA); @@ -53,7 +51,7 @@ public DihadaBinderOfWills(UUID ownerId, CardSetInfo setInfo) { ability.addEffect(new GainAbilityTargetEffect( IndestructibleAbility.getInstance(), Duration.UntilYourNextTurn ).setText(", and indestructible until your next turn.")); - ability.addTarget(new TargetCreaturePermanent(0, 1, legendarycreaturefilter, false)); + ability.addTarget(new TargetCreaturePermanent(0, 1, filter, false)); this.addAbility(ability); // -3: Reveal the top four cards of your library. @@ -62,16 +60,8 @@ public DihadaBinderOfWills(UUID ownerId, CardSetInfo setInfo) { this.addAbility(new LoyaltyAbility(new DihadaFilterEffect(), -3)); // -11: Gain control of all nonland permanents until end of turn. Untap them. They gain haste until end of turn. - ability = new LoyaltyAbility(new GainControlAllEffect(Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS_NON_LAND), -11); - ability.addEffect(new UntapAllEffect(StaticFilters.FILTER_PERMANENTS_NON_LAND).setText("untap them")); - ability.addEffect(new GainAbilityAllEffect( - HasteAbility.getInstance(), - Duration.EndOfTurn, - StaticFilters.FILTER_PERMANENTS_NON_LAND, - "they gain haste until end of turn" - )); - this.addAbility(ability); - + this.addAbility(new LoyaltyAbility(new GainControlAllUntapGainHasteEffect(StaticFilters.FILTER_PERMANENTS_NON_LAND), -11)); + // Dihada, Binder of Wills can be your commander. this.addAbility(CanBeYourCommanderAbility.getInstance()); } @@ -94,7 +84,7 @@ class DihadaFilterEffect extends RevealLibraryPickControllerEffect { legendaryfilter.add(SuperType.LEGENDARY.getPredicate()); } - public DihadaFilterEffect() { + DihadaFilterEffect() { super(4, Integer.MAX_VALUE, legendaryfilter, PutCards.HAND, PutCards.GRAVEYARD, false); staticText = "Reveal the top four cards of your library. " + "Put any number of legendary cards from among them into your hand and the rest into your graveyard. " + diff --git a/Mage.Sets/src/mage/cards/d/DimirCutpurse.java b/Mage.Sets/src/mage/cards/d/DimirCutpurse.java index 5dde825b1bd..bd68423ee31 100644 --- a/Mage.Sets/src/mage/cards/d/DimirCutpurse.java +++ b/Mage.Sets/src/mage/cards/d/DimirCutpurse.java @@ -54,7 +54,7 @@ class DimirCutpurseEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (damagedPlayer != null) { damagedPlayer.discard(1, false, false, source, game); } diff --git a/Mage.Sets/src/mage/cards/d/DimirDoppelganger.java b/Mage.Sets/src/mage/cards/d/DimirDoppelganger.java index 64f9732a428..49694c07f27 100644 --- a/Mage.Sets/src/mage/cards/d/DimirDoppelganger.java +++ b/Mage.Sets/src/mage/cards/d/DimirDoppelganger.java @@ -86,9 +86,8 @@ class DimirDoppelgangerEffect extends OneShotEffect { CopyApplier applier = new DimirDoppelgangerCopyApplier(); applier.apply(game, newBluePrint, source, dimirDoppelganger.getId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, newBluePrint, dimirDoppelganger.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); - Ability newAbility = source.copy(); + Ability newAbility = source.copy(); // TODO: why it copy new ability instead source? Some cards use it, some miss copyEffect.init(newAbility, game); game.addEffect(copyEffect, newAbility); } diff --git a/Mage.Sets/src/mage/cards/d/DimirSpybug.java b/Mage.Sets/src/mage/cards/d/DimirSpybug.java index c4b32af4921..b0566d38b22 100644 --- a/Mage.Sets/src/mage/cards/d/DimirSpybug.java +++ b/Mage.Sets/src/mage/cards/d/DimirSpybug.java @@ -2,7 +2,7 @@ package mage.cards.d; import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SurveilTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MenaceAbility; @@ -10,10 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; /** * @@ -35,7 +32,7 @@ public final class DimirSpybug extends CardImpl { this.addAbility(new MenaceAbility()); // Whenever you surveil, put a +1/+1 counter on Dimir Spybug. - this.addAbility(new DimirSpybugTriggeredAbility()); + this.addAbility(new SurveilTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); } private DimirSpybug(final DimirSpybug card) { @@ -47,36 +44,3 @@ public final class DimirSpybug extends CardImpl { return new DimirSpybug(this); } } - -class DimirSpybugTriggeredAbility extends TriggeredAbilityImpl { - - public DimirSpybugTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect( - CounterType.P1P1.createInstance() - ), false); - } - - private DimirSpybugTriggeredAbility(final DimirSpybugTriggeredAbility ability) { - super(ability); - } - - @Override - public DimirSpybugTriggeredAbility copy() { - return new DimirSpybugTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SURVEILED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.getControllerId()); - } - - @Override - public String getRule() { - return "Whenever you surveil, put a +1/+1 counter on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DinOfTheFireherd.java b/Mage.Sets/src/mage/cards/d/DinOfTheFireherd.java index 868fb0abd17..f9244f04725 100644 --- a/Mage.Sets/src/mage/cards/d/DinOfTheFireherd.java +++ b/Mage.Sets/src/mage/cards/d/DinOfTheFireherd.java @@ -78,7 +78,7 @@ class DinOfTheFireherdEffect extends OneShotEffect { int blackCreaturesControllerControls = game.getBattlefield().countAll(blackCreatureFilter, source.getControllerId(), game); int redCreaturesControllerControls = game.getBattlefield().countAll(redCreatureFilter, source.getControllerId(), game); - Player targetOpponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetOpponent != null) { Effect effect = new SacrificeEffect(new FilterControlledCreaturePermanent(), blackCreaturesControllerControls, "Target Opponent"); effect.setTargetPointer(new FixedTarget(targetOpponent.getId())); diff --git a/Mage.Sets/src/mage/cards/d/DinoDNA.java b/Mage.Sets/src/mage/cards/d/DinoDNA.java new file mode 100644 index 00000000000..37bb23a7fb7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DinoDNA.java @@ -0,0 +1,78 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetadjustment.TargetAdjuster; +import mage.util.CardUtil; + +/** + * + * @author jimga150 + */ +public final class DinoDNA extends CardImpl { + + public DinoDNA(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + + // Imprint -- {1}, {T}: Exile target creature card from a graveyard. Activate only as a sorcery. + // Based on Dimir Doppleganger + Ability imprintAbility = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect().setToSourceExileZone(true), new ManaCostsImpl<>("{1}")); + imprintAbility.addCost(new TapSourceCost()); + imprintAbility.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); + + this.addAbility(imprintAbility.setAbilityWord(AbilityWord.IMPRINT)); + + // {6}: Create a token that's a copy of target creature card exiled with Dino DNA, except it's a 6/6 green Dinosaur creature with trample. Activate only as a sorcery. + // Based on Croaking Counterpart and Bronzebeak Foragers + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + null, null, false, 1, false, false, + null, 6, 6, false + ); + effect.setOnlyColor(ObjectColor.GREEN); + effect.setOnlySubType(SubType.DINOSAUR); + + effect.addAdditionalAbilities(TrampleAbility.getInstance()); + effect.setText("Create a token that's a copy of target creature card exiled with {this}, except it's a 6/6 green Dinosaur creature with trample."); + Ability copyAbility = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{6}")); + copyAbility.setTargetAdjuster(DinoDNACopyAdjuster.instance); + + this.addAbility(copyAbility); + } + + private DinoDNA(final DinoDNA card) { + super(card); + } + + @Override + public DinoDNA copy() { + return new DinoDNA(this); + } +} + +enum DinoDNACopyAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + FilterCard filter = new FilterCreatureCard(); + ability.addTarget(new TargetCardInExile(filter, CardUtil.getExileZoneId(game, ability))); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DiplomacyOfTheWastes.java b/Mage.Sets/src/mage/cards/d/DiplomacyOfTheWastes.java index df8da77b284..e1c3c92d4cc 100644 --- a/Mage.Sets/src/mage/cards/d/DiplomacyOfTheWastes.java +++ b/Mage.Sets/src/mage/cards/d/DiplomacyOfTheWastes.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterNonlandCard; import mage.target.common.TargetOpponent; @@ -28,7 +27,7 @@ public final class DiplomacyOfTheWastes extends CardImpl { this.getSpellAbility().addEffect(new DiscardCardYouChooseTargetEffect(new FilterNonlandCard())); this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new LoseLifeTargetEffect(2), - new PermanentsOnTheBattlefieldCondition(new FilterControlledCreaturePermanent(SubType.WARRIOR, "Warrior")), + new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.WARRIOR, "Warrior")), "If you control a Warrior, that player loses 2 life")); } diff --git a/Mage.Sets/src/mage/cards/d/DireBlunderbuss.java b/Mage.Sets/src/mage/cards/d/DireBlunderbuss.java index e78d4d2576f..a33698c751d 100644 --- a/Mage.Sets/src/mage/cards/d/DireBlunderbuss.java +++ b/Mage.Sets/src/mage/cards/d/DireBlunderbuss.java @@ -89,7 +89,7 @@ class DireBlunderbussGainAbilityEffect extends ContinuousEffectImpl { public boolean apply(Game game, Ability source) { Permanent permanent = null; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage.Sets/src/mage/cards/d/Disarm.java b/Mage.Sets/src/mage/cards/d/Disarm.java index b4b722435ff..4c683f26764 100644 --- a/Mage.Sets/src/mage/cards/d/Disarm.java +++ b/Mage.Sets/src/mage/cards/d/Disarm.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -39,39 +38,40 @@ public final class Disarm extends CardImpl { return new Disarm(this); } - class DisarmEffect extends OneShotEffect { +} - public DisarmEffect() { - super(Outcome.UnboostCreature); - this.staticText = "Unattach all Equipment from target creature"; - } +class DisarmEffect extends OneShotEffect { - private DisarmEffect(final DisarmEffect effect) { - super(effect); - } + DisarmEffect() { + super(Outcome.UnboostCreature); + this.staticText = "Unattach all Equipment from target creature"; + } - @Override - public DisarmEffect copy() { - return new DisarmEffect(this); - } + private DisarmEffect(final DisarmEffect effect) { + super(effect); + } - @Override - public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); - if (creature != null) { - FilterPermanent creatureFilter = new FilterPermanent(); - creatureFilter.add(new PermanentIdPredicate(creature.getId())); + @Override + public DisarmEffect copy() { + return new DisarmEffect(this); + } - FilterPermanent equipmentFilter = new FilterPermanent(); - equipmentFilter.add(new AttachedToPredicate(creatureFilter)); - equipmentFilter.add(SubType.EQUIPMENT.getPredicate()); + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (creature != null) { + FilterPermanent creatureFilter = new FilterPermanent(); + creatureFilter.add(new PermanentIdPredicate(creature.getId())); - for (Permanent equipment : game.getBattlefield().getAllActivePermanents(equipmentFilter, game)) { - creature.removeAttachment(equipment.getId(), source, game); - } - return true; + FilterPermanent equipmentFilter = new FilterPermanent(); + equipmentFilter.add(new AttachedToPredicate(creatureFilter)); + equipmentFilter.add(SubType.EQUIPMENT.getPredicate()); + + for (Permanent equipment : game.getBattlefield().getAllActivePermanents(equipmentFilter, game)) { + creature.removeAttachment(equipment.getId(), source, game); } - return false; + return true; } + return false; } } diff --git a/Mage.Sets/src/mage/cards/d/DiscerningFinancier.java b/Mage.Sets/src/mage/cards/d/DiscerningFinancier.java index 83c3d30546a..5c61701cd1a 100644 --- a/Mage.Sets/src/mage/cards/d/DiscerningFinancier.java +++ b/Mage.Sets/src/mage/cards/d/DiscerningFinancier.java @@ -66,7 +66,7 @@ public final class DiscerningFinancier extends CardImpl { // {2}{W}: Choose another player. That player gains control of target Treasure you control. You draw a card. Ability ability = new SimpleActivatedAbility( new DiscerningFinancierEffect(), - new ManaCostsImpl("{2}{W}") + new ManaCostsImpl<>("{2}{W}") ); ability.addTarget(new TargetControlledPermanent(filter)); ability.addEffect(new DrawCardSourceControllerEffect(1, "you")); @@ -126,4 +126,4 @@ class DiscerningFinancierEffect extends OneShotEffect { return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DisinformationCampaign.java b/Mage.Sets/src/mage/cards/d/DisinformationCampaign.java index 95f7bc77848..e32fa4355ba 100644 --- a/Mage.Sets/src/mage/cards/d/DisinformationCampaign.java +++ b/Mage.Sets/src/mage/cards/d/DisinformationCampaign.java @@ -1,8 +1,8 @@ package mage.cards.d; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SurveilTriggeredAbility; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; @@ -11,9 +11,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.TargetController; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; import java.util.UUID; @@ -33,7 +30,7 @@ public final class DisinformationCampaign extends CardImpl { this.addAbility(ability); // Whenever you surveil, return Disinformation Campaign to its owner's hand. - this.addAbility(new DisinformationCampaignTriggeredAbility()); + this.addAbility(new SurveilTriggeredAbility(new ReturnToHandSourceEffect(true))); } private DisinformationCampaign(final DisinformationCampaign card) { @@ -45,34 +42,3 @@ public final class DisinformationCampaign extends CardImpl { return new DisinformationCampaign(this); } } - -class DisinformationCampaignTriggeredAbility extends TriggeredAbilityImpl { - - public DisinformationCampaignTriggeredAbility() { - super(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(true), false); - } - - private DisinformationCampaignTriggeredAbility(final DisinformationCampaignTriggeredAbility ability) { - super(ability); - } - - @Override - public DisinformationCampaignTriggeredAbility copy() { - return new DisinformationCampaignTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SURVEILED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.getControllerId()); - } - - @Override - public String getRule() { - return "Whenever you surveil, return {this} to its owner's hand."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DispersalShield.java b/Mage.Sets/src/mage/cards/d/DispersalShield.java index 73b6ce4f020..172ca4223c3 100644 --- a/Mage.Sets/src/mage/cards/d/DispersalShield.java +++ b/Mage.Sets/src/mage/cards/d/DispersalShield.java @@ -56,7 +56,7 @@ class DispersalShieldEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { DynamicValue amount = new HighestManaValueCount(); - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null && spell.getManaValue() <= amount.calculate(game, source, this)) { return game.getStack().counter(source.getFirstTarget(), source, game); } diff --git a/Mage.Sets/src/mage/cards/d/DisplacedDinosaurs.java b/Mage.Sets/src/mage/cards/d/DisplacedDinosaurs.java index f2cfdaf5300..83185aba85d 100644 --- a/Mage.Sets/src/mage/cards/d/DisplacedDinosaurs.java +++ b/Mage.Sets/src/mage/cards/d/DisplacedDinosaurs.java @@ -78,15 +78,15 @@ class DisplacedDinosaursEntersBattlefieldEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Permanent historic = ((EntersTheBattlefieldEvent) event).getTarget(); if (historic != null) { - TargetPointer historicTarget = new FixedTarget(historic.getId(), historic.getZoneChangeCounter(game) + 1); + TargetPointer blueprintTarget = new FixedTarget(historic.getId(), historic.getZoneChangeCounter(game) + 1); ContinuousEffect creatureEffect = new AddCardTypeTargetEffect(Duration.Custom, CardType.CREATURE); - creatureEffect.setTargetPointer(historicTarget); + creatureEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(creatureEffect, source); ContinuousEffect dinosaurEffect = new AddCardSubTypeTargetEffect(SubType.DINOSAUR, Duration.Custom); - dinosaurEffect.setTargetPointer(historicTarget); + dinosaurEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(dinosaurEffect, source); ContinuousEffect sevenSevenEffect = new SetBasePowerToughnessTargetEffect(7, 7, Duration.Custom); - sevenSevenEffect.setTargetPointer(historicTarget); + sevenSevenEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(sevenSevenEffect, source); } return false; diff --git a/Mage.Sets/src/mage/cards/d/Dispossess.java b/Mage.Sets/src/mage/cards/d/Dispossess.java index a83add2e891..05260d16bb0 100644 --- a/Mage.Sets/src/mage/cards/d/Dispossess.java +++ b/Mage.Sets/src/mage/cards/d/Dispossess.java @@ -51,7 +51,7 @@ class DispossessEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExi if (cardName == null) { return false; } - return super.applySearchAndExile(game, source, cardName, targetPointer.getFirst(game, source)); + return super.applySearchAndExile(game, source, cardName, getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DisruptingShoal.java b/Mage.Sets/src/mage/cards/d/DisruptingShoal.java index 94376172621..c9bb5dfca47 100644 --- a/Mage.Sets/src/mage/cards/d/DisruptingShoal.java +++ b/Mage.Sets/src/mage/cards/d/DisruptingShoal.java @@ -77,7 +77,7 @@ class DisruptingShoalCounterTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null && isManaValueEqual(source, spell.getManaValue())) { return game.getStack().counter(source.getFirstTarget(), source, game); } diff --git a/Mage.Sets/src/mage/cards/d/DistortingLens.java b/Mage.Sets/src/mage/cards/d/DistortingLens.java index fd5a5bc2900..b77f97118ce 100644 --- a/Mage.Sets/src/mage/cards/d/DistortingLens.java +++ b/Mage.Sets/src/mage/cards/d/DistortingLens.java @@ -59,7 +59,7 @@ class ChangeColorEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Permanent permanent = game.getPermanent(source.getSourceId()); - Permanent chosen = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent chosen = game.getPermanent(getTargetPointer().getFirst(game, source)); if (player != null && permanent != null) { ContinuousEffect effect = new BecomesColorTargetEffect(null, Duration.EndOfTurn); effect.setTargetPointer(new FixedTarget(chosen.getId(), game)); diff --git a/Mage.Sets/src/mage/cards/d/DisturbingPlot.java b/Mage.Sets/src/mage/cards/d/DisturbingPlot.java index d2a8cf2c636..7699b07574c 100644 --- a/Mage.Sets/src/mage/cards/d/DisturbingPlot.java +++ b/Mage.Sets/src/mage/cards/d/DisturbingPlot.java @@ -6,6 +6,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -16,14 +17,12 @@ import java.util.UUID; */ public final class DisturbingPlot extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public DisturbingPlot(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); // Return target creature card from a graveyard to its owner's hand. this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // Conspire this.addAbility(new ConspireAbility(ConspireAbility.ConspireTargets.ONE)); diff --git a/Mage.Sets/src/mage/cards/d/DivineIntervention.java b/Mage.Sets/src/mage/cards/d/DivineIntervention.java index 5b0d9206bfd..964928a4f2e 100644 --- a/Mage.Sets/src/mage/cards/d/DivineIntervention.java +++ b/Mage.Sets/src/mage/cards/d/DivineIntervention.java @@ -32,7 +32,7 @@ public final class DivineIntervention extends CardImpl { // Divine Intervention enters the battlefield with 2 intervention counters on it. Effect effect = new AddCountersSourceEffect(CounterType.INTERVENTION.createInstance(2)); - this.addAbility(new EntersBattlefieldAbility(effect, "with 2 intervention counters")); + this.addAbility(new EntersBattlefieldAbility(effect, "with two intervention counters on it")); // At the beginning of your upkeep, remove an intervention counter from Divine Intervention. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.INTERVENTION.createInstance()), TargetController.YOU, false)); @@ -50,69 +50,64 @@ public final class DivineIntervention extends CardImpl { return new DivineIntervention(this); } - class DivineInterventionAbility extends TriggeredAbilityImpl { +} - public DivineInterventionAbility() { - super(Zone.BATTLEFIELD, new DivineAbilityEffect2(), false); - } +class DivineInterventionAbility extends TriggeredAbilityImpl { - private DivineInterventionAbility(final DivineInterventionAbility ability) { - super(ability); - } - - @Override - public DivineInterventionAbility copy() { - return new DivineInterventionAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COUNTERS_REMOVED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return (event.getData().equals(CounterType.INTERVENTION.getName()) - && event.getTargetId().equals(this.getSourceId()) - && event.getPlayerId() != null - && event.getPlayerId() == this.getControllerId()); // the controller of this removed the counter - } - - @Override - public String getRule() { - return "When you remove the last intervention counter from {this}, the game is drawn."; - } + DivineInterventionAbility() { + super(Zone.BATTLEFIELD, new DivineInterventionDrawEffect(), false); + setTriggerPhrase("When you remove the last intervention counter from {this}, "); } - class DivineAbilityEffect2 extends OneShotEffect { + private DivineInterventionAbility(final DivineInterventionAbility ability) { + super(ability); + } - public DivineAbilityEffect2() { - super(Outcome.Neutral); - this.staticText = "you draw the game"; - } + @Override + public DivineInterventionAbility copy() { + return new DivineInterventionAbility(this); + } - private DivineAbilityEffect2(final DivineAbilityEffect2 effect) { - super(effect); - } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_REMOVED; + } - @Override - public DivineAbilityEffect2 copy() { - return new DivineAbilityEffect2(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (controller != null - && sourcePermanent != null) { - if (game.getState().getZone(sourcePermanent.getId()) == Zone.BATTLEFIELD - && sourcePermanent.getCounters(game).getCount(CounterType.INTERVENTION) == 0) { - game.setDraw(controller.getId()); - } - return true; - } + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getData().equals(CounterType.INTERVENTION.getName()) + || !this.getSourceId().equals(event.getTargetId()) + || !this.getControllerId().equals(event.getPlayerId())) { return false; } + Permanent permanent = game.getPermanent(this.getSourceId()); + return permanent != null && permanent.getCounters(game).getCount(CounterType.INTERVENTION) == 0; + } +} + +class DivineInterventionDrawEffect extends OneShotEffect { + + DivineInterventionDrawEffect() { + super(Outcome.Neutral); + this.staticText = "the game is a draw"; + } + + private DivineInterventionDrawEffect(final DivineInterventionDrawEffect effect) { + super(effect); + } + + @Override + public DivineInterventionDrawEffect copy() { + return new DivineInterventionDrawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + game.setDraw(controller.getId()); + return true; + } + return false; } } diff --git a/Mage.Sets/src/mage/cards/d/Donate.java b/Mage.Sets/src/mage/cards/d/Donate.java index c8636d12ef2..38a79b8d1ab 100644 --- a/Mage.Sets/src/mage/cards/d/Donate.java +++ b/Mage.Sets/src/mage/cards/d/Donate.java @@ -1,22 +1,13 @@ - package mage.cards.d; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.common.TargetPlayerGainControlTargetPermanentEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetControlledPermanent; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** * @@ -28,7 +19,7 @@ public final class Donate extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{U}"); // Target player gains control of target permanent you control. - this.getSpellAbility().addEffect(new DonateEffect()); + this.getSpellAbility().addEffect(new TargetPlayerGainControlTargetPermanentEffect()); this.getSpellAbility().addTarget(new TargetPlayer()); this.getSpellAbility().addTarget(new TargetControlledPermanent()); } @@ -42,33 +33,3 @@ public final class Donate extends CardImpl { return new Donate(this); } } - -class DonateEffect extends OneShotEffect { - - DonateEffect() { - super(Outcome.Detriment); - this.staticText = "Target player gains control of target permanent you control"; - } - - private DonateEffect(final DonateEffect effect) { - super(effect); - } - - @Override - public DonateEffect copy() { - return new DonateEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); - Permanent permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (targetPlayer != null && permanent != null) { - ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, true, targetPlayer.getId()); - effect.setTargetPointer(new FixedTarget(permanent, game)); - game.addEffect(effect, source); - } - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/d/DonnaNoble.java b/Mage.Sets/src/mage/cards/d/DonnaNoble.java new file mode 100644 index 00000000000..f3ad5f05598 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DonnaNoble.java @@ -0,0 +1,114 @@ +package mage.cards.d; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.constants.*; +import mage.abilities.keyword.SoulbondAbility; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.DamagedBatchForOnePermanentEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +/** + * + * @author jimga150 + */ +public final class DonnaNoble extends CardImpl { + + public DonnaNoble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Soulbond + this.addAbility(new SoulbondAbility()); + + // Whenever Donna or a creature it's paired with is dealt damage, Donna deals that much damage to target opponent. + this.addAbility(new DonnaNobleTriggeredAbility()); + + // If Donna is paired with another creature and they are both dealt damage at the same time, + // the second ability triggers twice. (2023-10-13) + + // Donna's ability triggers when either creature is dealt damage even if one or both were dealt lethal damage. + // (2023-10-13) + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + + } + + private DonnaNoble(final DonnaNoble card) { + super(card); + } + + @Override + public DonnaNoble copy() { + return new DonnaNoble(this); + } +} +// Based on DealtDamageToSourceTriggeredAbility, except this uses DamagedBatchForOnePermanentEvent, +// which batches all damage dealt at the same time on a permanent-by-permanent basis +class DonnaNobleTriggeredAbility extends TriggeredAbilityImpl { + + DonnaNobleTriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MUCH)); + this.addTarget(new TargetOpponent()); + this.setTriggerPhrase("Whenever {this} or a creature it's paired with is dealt damage, "); + } + + private DonnaNobleTriggeredAbility(final DonnaNobleTriggeredAbility ability) { + super(ability); + } + + @Override + public DonnaNobleTriggeredAbility copy() { + return new DonnaNobleTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + + // check if the permanent is Donna or its paired card + if (!CardUtil.getEventTargets(dEvent).contains(getSourceId())){ + Permanent paired; + Permanent permanent = game.getPermanent(getSourceId()); + if (permanent != null && permanent.getPairedCard() != null) { + paired = permanent.getPairedCard().getPermanent(game); + if (paired == null || paired.getPairedCard() == null || !paired.getPairedCard().equals(new MageObjectReference(permanent, game))) { + return false; + } + } else { + return false; + } + if (!CardUtil.getEventTargets(dEvent).contains(paired.getId())){ + return false; + } + } + + int damage = dEvent.getAmount(); + if (damage < 1) { + return false; + } + this.getEffects().setValue("damage", damage); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DragonsApproach.java b/Mage.Sets/src/mage/cards/d/DragonsApproach.java index 9e4c28d7e41..0ae6c70af0c 100644 --- a/Mage.Sets/src/mage/cards/d/DragonsApproach.java +++ b/Mage.Sets/src/mage/cards/d/DragonsApproach.java @@ -7,6 +7,9 @@ import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -22,11 +25,15 @@ import java.util.UUID; /** * @author TheElk801 + * @modified tiera3 - added cardHint */ public final class DragonsApproach extends CardImpl { private static final FilterCard filter = new FilterCreatureCard("Dragon creature card"); private static final FilterCard filter2 = new FilterCard("cards named Dragon's Approach"); + private static final Hint hint = new ValueHint( + "Cards named Dragon's Approach in your graveyard", new CardsInControllerGraveyardCount(filter2) + ); static { filter.add(SubType.DRAGON.getPredicate()); @@ -45,6 +52,7 @@ public final class DragonsApproach extends CardImpl { "exile {this} and four cards named Dragon's Approach from your graveyard" ) )); + this.getSpellAbility().addHint(hint); // A deck can have any number of cards named Dragon's Approach. this.getSpellAbility().addEffect(new InfoEffect( diff --git a/Mage.Sets/src/mage/cards/d/DragonsFire.java b/Mage.Sets/src/mage/cards/d/DragonsFire.java index f0a3843881f..3492742a074 100644 --- a/Mage.Sets/src/mage/cards/d/DragonsFire.java +++ b/Mage.Sets/src/mage/cards/d/DragonsFire.java @@ -186,7 +186,7 @@ class DragonsFireEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetedPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetedPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetedPermanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DrainPower.java b/Mage.Sets/src/mage/cards/d/DrainPower.java index 1c1d5920d8d..131d222efdf 100644 --- a/Mage.Sets/src/mage/cards/d/DrainPower.java +++ b/Mage.Sets/src/mage/cards/d/DrainPower.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.ArrayList; @@ -18,7 +17,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.ManaPoolItem; @@ -112,7 +111,7 @@ class DrainPowerEffect extends OneShotEffect { Permanent permanent; if (permList.size() > 1 || target != null) { FilterLandPermanent filter2 = new FilterLandPermanent("land you control to tap for mana (remaining: " + permList.size() + ')'); - filter2.add(new PermanentInListPredicate(permList)); + filter2.add(new PermanentReferenceInCollectionPredicate(permList, game)); target = new TargetPermanent(1, 1, filter2, true); while (!target.isChosen() && target.canChoose(targetPlayer.getId(), source, game) && targetPlayer.canRespond()) { targetPlayer.chooseTarget(Outcome.Neutral, target, source, game); diff --git a/Mage.Sets/src/mage/cards/d/DralnuLichLord.java b/Mage.Sets/src/mage/cards/d/DralnuLichLord.java index b8ddf74c981..42daa2c7573 100644 --- a/Mage.Sets/src/mage/cards/d/DralnuLichLord.java +++ b/Mage.Sets/src/mage/cards/d/DralnuLichLord.java @@ -116,7 +116,7 @@ class DralnuLichLordFlashbackEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); diff --git a/Mage.Sets/src/mage/cards/d/DranaTheLastBloodchief.java b/Mage.Sets/src/mage/cards/d/DranaTheLastBloodchief.java index 4438442dd41..cc908e2334c 100644 --- a/Mage.Sets/src/mage/cards/d/DranaTheLastBloodchief.java +++ b/Mage.Sets/src/mage/cards/d/DranaTheLastBloodchief.java @@ -86,7 +86,7 @@ class DranaTheLastBloodchiefEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || player == null || controller.getGraveyard().count(filter, game) < 1) { return false; diff --git a/Mage.Sets/src/mage/cards/d/DranasChosen.java b/Mage.Sets/src/mage/cards/d/DranasChosen.java index 1e1ec1258f5..8356444a3f3 100644 --- a/Mage.Sets/src/mage/cards/d/DranasChosen.java +++ b/Mage.Sets/src/mage/cards/d/DranasChosen.java @@ -1,36 +1,21 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; import mage.game.permanent.token.ZombieToken; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * * @author fireshoes */ public final class DranasChosen extends CardImpl { - - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } public DranasChosen(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); @@ -41,10 +26,7 @@ public final class DranasChosen extends CardImpl { this.toughness = new MageInt(2); // Cohort — {T}, Tap an untapped Ally you control: Create a tapped 2/2 black Zombie creature token. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieToken(), 1, true, false), new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); - ability.setAbilityWord(AbilityWord.COHORT); - this.addAbility(ability); + this.addAbility(new CohortAbility(new CreateTokenEffect(new ZombieToken(), 1, true, false))); } private DranasChosen(final DranasChosen card) { diff --git a/Mage.Sets/src/mage/cards/d/DreadSlaver.java b/Mage.Sets/src/mage/cards/d/DreadSlaver.java index cd51a4dc076..8b3cf98e16e 100644 --- a/Mage.Sets/src/mage/cards/d/DreadSlaver.java +++ b/Mage.Sets/src/mage/cards/d/DreadSlaver.java @@ -64,7 +64,7 @@ class DreadSlaverEffect extends OneShotEffect { if (controller == null) { return false; } - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { ContinuousEffect effect = new AddCreatureTypeAdditionEffect(SubType.ZOMBIE, true); diff --git a/Mage.Sets/src/mage/cards/d/DreadWight.java b/Mage.Sets/src/mage/cards/d/DreadWight.java index 295f670bc26..34aaf80fe29 100644 --- a/Mage.Sets/src/mage/cards/d/DreadWight.java +++ b/Mage.Sets/src/mage/cards/d/DreadWight.java @@ -121,7 +121,7 @@ class DreadWightEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { // add paralyzation counter Effect effect = new AddCountersTargetEffect(CounterType.PARALYZATION.createInstance()); diff --git a/Mage.Sets/src/mage/cards/d/DreamTides.java b/Mage.Sets/src/mage/cards/d/DreamTides.java index 35fc5f8af57..46d2dc3c56a 100644 --- a/Mage.Sets/src/mage/cards/d/DreamTides.java +++ b/Mage.Sets/src/mage/cards/d/DreamTides.java @@ -75,7 +75,7 @@ class DreamTidesEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && sourcePermanent != null) { int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); diff --git a/Mage.Sets/src/mage/cards/d/DroolingOgre.java b/Mage.Sets/src/mage/cards/d/DroolingOgre.java index ec4d11cab85..edd68e7233f 100644 --- a/Mage.Sets/src/mage/cards/d/DroolingOgre.java +++ b/Mage.Sets/src/mage/cards/d/DroolingOgre.java @@ -1,28 +1,19 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.Spell; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * - * @author wetterlicht + * @author xenohedron */ public final class DroolingOgre extends CardImpl { @@ -33,7 +24,8 @@ public final class DroolingOgre extends CardImpl { this.toughness = new MageInt(3); // Whenever a player casts an artifact spell, that player gains control of Drooling Ogre. - this.addAbility(new DroolingOgreTriggeredAbility()); + this.addAbility(new SpellCastAllTriggeredAbility(new TargetPlayerGainControlSourceEffect(), + StaticFilters.FILTER_SPELL_AN_ARTIFACT, false, SetTargetPointer.PLAYER)); } private DroolingOgre(final DroolingOgre card) { @@ -45,72 +37,4 @@ public final class DroolingOgre extends CardImpl { return new DroolingOgre(this); } - private static class DroolingOgreEffect extends OneShotEffect { - - DroolingOgreEffect() { - super(Outcome.GainControl); - this.staticText = "that player gains control of {this}"; - } - - private DroolingOgreEffect(final DroolingOgreEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Player newController = game.getPlayer(this.getTargetPointer().getFirst(game, source)); - if (newController != null - && controller != null - && !controller.equals(newController)) { - ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, newController.getId()); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), game)); - game.addEffect(effect, source); - return true; - } - return false; - } - - @Override - public DroolingOgreEffect copy() { - return new DroolingOgreEffect(this); - } - - } - - class DroolingOgreTriggeredAbility extends TriggeredAbilityImpl { - - public DroolingOgreTriggeredAbility() { - super(Zone.BATTLEFIELD, new DroolingOgreEffect(), false); - } - - private DroolingOgreTriggeredAbility(final DroolingOgreTriggeredAbility ability) { - super(ability); - } - - @Override - public DroolingOgreTriggeredAbility copy() { - return new DroolingOgreTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell != null && spell.isArtifact(game)) { - this.getEffects().get(0).setTargetPointer(new FixedTarget(event.getPlayerId())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever a player casts an artifact spell, that player gains control of {this}."; - } - } } diff --git a/Mage.Sets/src/mage/cards/d/DualNature.java b/Mage.Sets/src/mage/cards/d/DualNature.java index f43406eb629..f4bebc48649 100644 --- a/Mage.Sets/src/mage/cards/d/DualNature.java +++ b/Mage.Sets/src/mage/cards/d/DualNature.java @@ -87,7 +87,7 @@ class DualNatureCreateTokenEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(permanent.getControllerId()); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); Object object = game.getState().getValue(CardUtil.getCardZoneString("_tokensCreated", source.getSourceId(), game)); Set tokensCreated; diff --git a/Mage.Sets/src/mage/cards/d/Duplicant.java b/Mage.Sets/src/mage/cards/d/Duplicant.java index 02f11bb762b..a49eca838fa 100644 --- a/Mage.Sets/src/mage/cards/d/Duplicant.java +++ b/Mage.Sets/src/mage/cards/d/Duplicant.java @@ -76,7 +76,7 @@ class DuplicantExileTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (permanent != null && sourceObject instanceof Permanent) { if (permanent.moveToExile(null, null, source, game) diff --git a/Mage.Sets/src/mage/cards/d/DuskanaTheRageMother.java b/Mage.Sets/src/mage/cards/d/DuskanaTheRageMother.java new file mode 100644 index 00000000000..6c0510d5041 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuskanaTheRageMother.java @@ -0,0 +1,55 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.BasePowerPredicate; +import mage.filter.predicate.mageobject.BaseToughnessPredicate; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class DuskanaTheRageMother extends CardImpl { + + static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control with base power and toughness 2/2"); + + static { + filter.add(new BasePowerPredicate(ComparisonType.EQUAL_TO, 2)); + filter.add(new BaseToughnessPredicate(ComparisonType.EQUAL_TO, 2)); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public DuskanaTheRageMother(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}{W}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When Duskana, the Rage Mother enters the battlefield, draw a card for each creature you control with base power and toughness 2/2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(new PermanentsOnBattlefieldCount(filter)))); + + // Whenever a creature you control with base power and toughness 2/2 attacks, it gets +3/+3 until end of turn. + this.addAbility(new AttacksAllTriggeredAbility( + new BoostTargetEffect(3, 3), false, filter, SetTargetPointer.PERMANENT, false + )); + } + + private DuskanaTheRageMother(final DuskanaTheRageMother card) { + super(card); + } + + @Override + public DuskanaTheRageMother copy() { + return new DuskanaTheRageMother(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EaterOfVirtue.java b/Mage.Sets/src/mage/cards/e/EaterOfVirtue.java index 85c13c02805..d22fa0f8f27 100644 --- a/Mage.Sets/src/mage/cards/e/EaterOfVirtue.java +++ b/Mage.Sets/src/mage/cards/e/EaterOfVirtue.java @@ -100,7 +100,7 @@ class EaterOfVirtueExileEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Permanent eaterOfVirtue = game.getPermanent(source.getSourceId()); - Card exiledCard = game.getCard(targetPointer.getFirst(game, source)); + Card exiledCard = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && eaterOfVirtue != null && exiledCard != null) { diff --git a/Mage.Sets/src/mage/cards/e/EchoChamber.java b/Mage.Sets/src/mage/cards/e/EchoChamber.java index 38c57315549..34f9ca9b9bf 100644 --- a/Mage.Sets/src/mage/cards/e/EchoChamber.java +++ b/Mage.Sets/src/mage/cards/e/EchoChamber.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetOpponentsChoicePermanent; @@ -27,15 +27,13 @@ import java.util.UUID; */ public final class EchoChamber extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - public EchoChamber(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // {4}, {tap}: An opponent chooses target creature they control. Create a token that's a copy of that creature. That token gains haste until end of turn. Exile the token at the beginning of the next end step. Activate this ability only any time you could cast a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new EchoChamberCreateTokenEffect(), new GenericManaCost(4)); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EchoMage.java b/Mage.Sets/src/mage/cards/e/EchoMage.java index bd4e5f24e64..4f0450aebb6 100644 --- a/Mage.Sets/src/mage/cards/e/EchoMage.java +++ b/Mage.Sets/src/mage/cards/e/EchoMage.java @@ -92,7 +92,7 @@ class EchoMageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { spell.createCopyOnStack(game, source, source.getControllerId(), true, 2); return true; diff --git a/Mage.Sets/src/mage/cards/e/EchoingCourage.java b/Mage.Sets/src/mage/cards/e/EchoingCourage.java index aaa380283b1..819a311b6fd 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingCourage.java +++ b/Mage.Sets/src/mage/cards/e/EchoingCourage.java @@ -61,7 +61,7 @@ class EchoingCourageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); if (CardUtil.haveEmptyName(targetPermanent)) { diff --git a/Mage.Sets/src/mage/cards/e/EchoingDecay.java b/Mage.Sets/src/mage/cards/e/EchoingDecay.java index b9910b06dc0..74dc1b019af 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingDecay.java +++ b/Mage.Sets/src/mage/cards/e/EchoingDecay.java @@ -60,7 +60,7 @@ class EchoingDecayEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); if (CardUtil.haveEmptyName(targetPermanent)) { diff --git a/Mage.Sets/src/mage/cards/e/EchoingDeeps.java b/Mage.Sets/src/mage/cards/e/EchoingDeeps.java index 91db220de8e..82518e08484 100644 --- a/Mage.Sets/src/mage/cards/e/EchoingDeeps.java +++ b/Mage.Sets/src/mage/cards/e/EchoingDeeps.java @@ -99,7 +99,6 @@ class EchoingDeepsEffect extends OneShotEffect { CopyApplier applier = new EchoingDeepsApplier(); applier.apply(game, newBluePrint, source, source.getSourceId()); CopyEffect copyEffect = new CopyEffect(Duration.WhileOnBattlefield, newBluePrint, source.getSourceId()); - copyEffect.newId(); copyEffect.setApplier(applier); copyEffect.init(source, game); game.addEffect(copyEffect, source); diff --git a/Mage.Sets/src/mage/cards/e/EdgewallInn.java b/Mage.Sets/src/mage/cards/e/EdgewallInn.java index 2e72ec5aaea..4c7058081b6 100644 --- a/Mage.Sets/src/mage/cards/e/EdgewallInn.java +++ b/Mage.Sets/src/mage/cards/e/EdgewallInn.java @@ -49,7 +49,7 @@ public final class EdgewallInn extends CardImpl { // {3}, {T}, Sacrifice Edgewall Inn: Return target card that has an Adventure from your graveyard to your hand. Ability ability = new SimpleActivatedAbility( new ReturnFromGraveyardToHandTargetEffect(), - new ManaCostsImpl("{3}") + new ManaCostsImpl<>("{3}") ); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); diff --git a/Mage.Sets/src/mage/cards/e/EdificeOfAuthority.java b/Mage.Sets/src/mage/cards/e/EdificeOfAuthority.java index 3f1b987f495..3e7eeabc30c 100644 --- a/Mage.Sets/src/mage/cards/e/EdificeOfAuthority.java +++ b/Mage.Sets/src/mage/cards/e/EdificeOfAuthority.java @@ -128,7 +128,7 @@ class EdificeOfAuthorityRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/e/EightAndAHalfTails.java b/Mage.Sets/src/mage/cards/e/EightAndAHalfTails.java index 59266cf81fb..cbcee74440f 100644 --- a/Mage.Sets/src/mage/cards/e/EightAndAHalfTails.java +++ b/Mage.Sets/src/mage/cards/e/EightAndAHalfTails.java @@ -1,4 +1,3 @@ - package mage.cards.e; import java.util.UUID; @@ -17,7 +16,6 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.constants.SuperType; import mage.constants.Zone; -import mage.target.Target; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSpellOrPermanent; @@ -38,14 +36,12 @@ public final class EightAndAHalfTails extends CardImpl { // {1}{W}: Target permanent you control gains protection from white until end of turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilityTargetEffect(ProtectionAbility.from(ObjectColor.WHITE), Duration.EndOfTurn), new ManaCostsImpl<>("{1}{W}")); - Target target = new TargetControlledPermanent(); - ability.addTarget(target); + ability.addTarget(new TargetControlledPermanent()); this.addAbility(ability); // {1}: Target spell or permanent becomes white until end of turn. - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new BecomesColorTargetEffect(ObjectColor.WHITE, Duration.EndOfTurn, "Target spell or permanent becomes white until end of turn"), new ManaCostsImpl<>("{1}")); - target = new TargetSpellOrPermanent(); - ability.addTarget(target); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesColorTargetEffect(ObjectColor.WHITE, Duration.EndOfTurn) + .setText("Target spell or permanent becomes white until end of turn"), new ManaCostsImpl<>("{1}")); + ability.addTarget(new TargetSpellOrPermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EivorBattleReady.java b/Mage.Sets/src/mage/cards/e/EivorBattleReady.java new file mode 100644 index 00000000000..77c6635f16f --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EivorBattleReady.java @@ -0,0 +1,59 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.VigilanceAbility; +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.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EivorBattleReady extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.EQUIPMENT)); + private static final Hint hint = new ValueHint("Equipment you control", xValue); + + public EivorBattleReady(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Eivor, Battle-Ready attacks, it deals damage equal to the number of Equipment you control to each opponent. + this.addAbility(new AttacksTriggeredAbility(new DamagePlayersEffect(xValue, TargetController.OPPONENT) + .setText("it deals damage equal to the number of Equipment you control to each opponent"))); + } + + private EivorBattleReady(final EivorBattleReady card) { + super(card); + } + + @Override + public EivorBattleReady copy() { + return new EivorBattleReady(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElderCathar.java b/Mage.Sets/src/mage/cards/e/ElderCathar.java index 20b1e8fe63f..7c0590f1ac5 100644 --- a/Mage.Sets/src/mage/cards/e/ElderCathar.java +++ b/Mage.Sets/src/mage/cards/e/ElderCathar.java @@ -66,7 +66,7 @@ class ElderCatharAddCountersTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { if (counter != null) { if (permanent.hasSubtype(SubType.HUMAN, game)) { diff --git a/Mage.Sets/src/mage/cards/e/EldraziMonument.java b/Mage.Sets/src/mage/cards/e/EldraziMonument.java index 27ae71d7e1c..d0fe929c77a 100644 --- a/Mage.Sets/src/mage/cards/e/EldraziMonument.java +++ b/Mage.Sets/src/mage/cards/e/EldraziMonument.java @@ -3,8 +3,7 @@ package mage.cards.e; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.FlyingAbility; @@ -13,9 +12,13 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; import mage.constants.TargetController; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetSacrifice; import java.util.UUID; @@ -43,10 +46,7 @@ public final class EldraziMonument extends CardImpl { this.addAbility(ability); // At the beginning of your upkeep, sacrifice a creature. If you can't, sacrifice Eldrazi Monument. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new SacrificeSourceUnlessPaysEffect(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT) - ).setText("sacrifice a creature. If you can't, sacrifice {this}"), TargetController.YOU, false - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new EldraziMonumentEffect(), TargetController.YOU, false)); } private EldraziMonument(final EldraziMonument card) { @@ -58,3 +58,44 @@ public final class EldraziMonument extends CardImpl { return new EldraziMonument(this); } } + +class EldraziMonumentEffect extends OneShotEffect { + + EldraziMonumentEffect() { + super(Outcome.Sacrifice); + staticText = "sacrifice a creature. If you can't, sacrifice {this}"; + } + + private EldraziMonumentEffect(final EldraziMonumentEffect effect) { + super(effect); + } + + @Override + public EldraziMonumentEffect copy() { + return new EldraziMonumentEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + // Sacrifice a creature + TargetSacrifice target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); + if (target.canChoose(controller.getId(), source, game)) { + controller.choose(Outcome.Sacrifice, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + return permanent.sacrifice(source, game); + } + } + // If you can't, sacrifice Eldrazi Monument + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + return permanent.sacrifice(source, game); + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/e/EldritchPact.java b/Mage.Sets/src/mage/cards/e/EldritchPact.java index cfd51b639f7..4d23d39f690 100644 --- a/Mage.Sets/src/mage/cards/e/EldritchPact.java +++ b/Mage.Sets/src/mage/cards/e/EldritchPact.java @@ -53,7 +53,7 @@ class EldritchPactEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/e/ElrondOfTheWhiteCouncil.java b/Mage.Sets/src/mage/cards/e/ElrondOfTheWhiteCouncil.java index f921f460dd1..942bc6fcf44 100644 --- a/Mage.Sets/src/mage/cards/e/ElrondOfTheWhiteCouncil.java +++ b/Mage.Sets/src/mage/cards/e/ElrondOfTheWhiteCouncil.java @@ -1,6 +1,7 @@ package mage.cards.e; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -23,6 +24,7 @@ import mage.target.targetpointer.FixedTargets; import mage.target.targetpointer.TargetPointer; import mage.util.CardUtil; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -127,16 +129,16 @@ class ElrondOfWhiteCouncilEffect extends OneShotEffect { } // You gain control of each creature chosen this way, and they gain "This creature can't attack its owner." - TargetPointer pointer = new FixedTargets(chosenCreatures.stream().collect(Collectors.toList()), game); + TargetPointer blueprintTarget = new FixedTargets(new ArrayList<>(chosenCreatures), game); game.addEffect(new GainControlTargetEffect( Duration.WhileOnBattlefield - ).setTargetPointer(pointer), source); + ).setTargetPointer(blueprintTarget.copy()), source); game.addEffect(new GainAbilityTargetEffect( new SimpleStaticAbility(new CantAttackItsOwnerEffect()), Duration.WhileOnBattlefield - ).setTargetPointer(pointer), source); + ).setTargetPointer(blueprintTarget.copy()), source); // Need to process the control change. game.getState().processAction(game); diff --git a/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java b/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java index 706aacafef4..970ad2a9d33 100644 --- a/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java +++ b/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java @@ -95,6 +95,11 @@ class ElsewhereFlaskContinuousEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + "_ElsewhereFlask")); + if (choice == null) { + discard(); + return; + } + switch (choice) { case FOREST: dependencyTypes.add(DependencyType.BecomeForest); diff --git a/Mage.Sets/src/mage/cards/e/ElvishBranchbender.java b/Mage.Sets/src/mage/cards/e/ElvishBranchbender.java index 7fb1feb8e4e..f51ce5ac83b 100644 --- a/Mage.Sets/src/mage/cards/e/ElvishBranchbender.java +++ b/Mage.Sets/src/mage/cards/e/ElvishBranchbender.java @@ -82,7 +82,7 @@ class ElvishBranchbenderEffect extends OneShotEffect { new ElvishBranchbenderToken(xValue), false, false, Duration.EndOfTurn) .withDurationRuleAtStart(true); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); return false; } @@ -104,4 +104,4 @@ class ElvishBranchbenderToken extends TokenImpl { public ElvishBranchbenderToken copy() { return new ElvishBranchbenderToken(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/e/ElvishVatkeeper.java b/Mage.Sets/src/mage/cards/e/ElvishVatkeeper.java index d81b28a8c0c..9f705d6455f 100644 --- a/Mage.Sets/src/mage/cards/e/ElvishVatkeeper.java +++ b/Mage.Sets/src/mage/cards/e/ElvishVatkeeper.java @@ -5,20 +5,17 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; import mage.abilities.effects.common.TransformTargetEffect; import mage.abilities.effects.keyword.IncubateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import java.util.UUID; @@ -49,7 +46,7 @@ public final class ElvishVatkeeper extends CardImpl { // {5}: Transform target Incubator token you control. Double the number of +1/+1 counters on it. Ability ability = new SimpleActivatedAbility(new TransformTargetEffect(), new GenericManaCost(5)); - ability.addEffect(new ElvishVatkeeperEffect()); + ability.addEffect(new DoubleCountersTargetEffect(CounterType.P1P1)); ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } @@ -63,28 +60,3 @@ public final class ElvishVatkeeper extends CardImpl { return new ElvishVatkeeper(this); } } - -class ElvishVatkeeperEffect extends OneShotEffect { - - ElvishVatkeeperEffect() { - super(Outcome.Benefit); - staticText = "double the number of +1/+1 counters on it"; - } - - private ElvishVatkeeperEffect(final ElvishVatkeeperEffect effect) { - super(effect); - } - - @Override - public ElvishVatkeeperEffect copy() { - return new ElvishVatkeeperEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - return permanent != null && permanent.addCounters(CounterType.P1P1.createInstance( - permanent.getCounters(game).getCount(CounterType.P1P1) - ), source.getControllerId(), source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/e/EmeriaShepherd.java b/Mage.Sets/src/mage/cards/e/EmeriaShepherd.java index 547a6b377c5..8670a3fd121 100644 --- a/Mage.Sets/src/mage/cards/e/EmeriaShepherd.java +++ b/Mage.Sets/src/mage/cards/e/EmeriaShepherd.java @@ -86,7 +86,7 @@ class EmeriaShepherdReturnToHandTargetEffect extends OneShotEffect { && controller.chooseUse(Outcome.PutCardInPlay, "Put the card to battlefield instead?", source, game)) { toZone = Zone.BATTLEFIELD; } - return controller.moveCards(new CardsImpl(targetPointer.getTargets(game, source)), toZone, source, game); + return controller.moveCards(new CardsImpl(getTargetPointer().getTargets(game, source)), toZone, source, game); } } diff --git a/Mage.Sets/src/mage/cards/e/EmissaryOfHope.java b/Mage.Sets/src/mage/cards/e/EmissaryOfHope.java index 9abaade88aa..cb779e32273 100644 --- a/Mage.Sets/src/mage/cards/e/EmissaryOfHope.java +++ b/Mage.Sets/src/mage/cards/e/EmissaryOfHope.java @@ -64,7 +64,7 @@ class EmissaryOfHopeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player sourcePlayer = game.getPlayer(source.getControllerId()); if (targetPlayer != null && sourcePlayer != null) { int amount = game.getBattlefield().count(filter, targetPlayer.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/e/EndlessObedience.java b/Mage.Sets/src/mage/cards/e/EndlessObedience.java index 74e80dc39de..f696f7f5717 100644 --- a/Mage.Sets/src/mage/cards/e/EndlessObedience.java +++ b/Mage.Sets/src/mage/cards/e/EndlessObedience.java @@ -7,6 +7,7 @@ import mage.abilities.keyword.ConvokeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -24,7 +25,7 @@ public final class EndlessObedience extends CardImpl { this.addAbility(new ConvokeAbility()); // Put target creature card from a graveyard onto the battlefield under your control. - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); } diff --git a/Mage.Sets/src/mage/cards/e/EnergyTap.java b/Mage.Sets/src/mage/cards/e/EnergyTap.java index c13981070e3..a1c89e44828 100644 --- a/Mage.Sets/src/mage/cards/e/EnergyTap.java +++ b/Mage.Sets/src/mage/cards/e/EnergyTap.java @@ -69,7 +69,7 @@ class EnergyTapEffect extends OneShotEffect { } boolean applied = false; - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { applied = targetCreature.tap(source, game); if (applied) { diff --git a/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java b/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java index d6a344b91e4..4c01db95901 100644 --- a/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java +++ b/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.Permanent; @@ -47,8 +48,6 @@ public final class EntrailsFeaster extends CardImpl { class EntrailsFeasterEffect extends OneShotEffect { - private static final FilterCreatureCard filter = new FilterCreatureCard("creature card from a graveyard"); - public EntrailsFeasterEffect() { super(Outcome.Detriment); this.staticText = "you may exile a creature card from a graveyard. If you do, put a +1/+1 counter on {this}. If you don't, tap {this}"; @@ -68,7 +67,7 @@ class EntrailsFeasterEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && source.getSourceId() != null) { Permanent sourceObject = source.getSourcePermanentIfItStillExists(game); - TargetCardInGraveyard target = new TargetCardInGraveyard(filter); + TargetCardInGraveyard target = new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD); target.withNotTarget(true); if (target.canChoose(controller.getId(), source, game) && controller.chooseUse(outcome, "Exile a creature card from a graveyard?", source, game)) { if (controller.choose(Outcome.Exile, target, source, game)) { diff --git a/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java b/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java index 05a278a0252..bb2ab0da234 100644 --- a/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java +++ b/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java @@ -61,7 +61,7 @@ class EntrapmentManeuverSacrificeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/e/EssenceSliver.java b/Mage.Sets/src/mage/cards/e/EssenceSliver.java index e8dd26d3150..3dc7c6e3dfc 100644 --- a/Mage.Sets/src/mage/cards/e/EssenceSliver.java +++ b/Mage.Sets/src/mage/cards/e/EssenceSliver.java @@ -103,7 +103,7 @@ class EssenceSliverEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controllerOfSliver = game.getPlayer(targetPointer.getFirst(game, source)); + Player controllerOfSliver = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controllerOfSliver != null) { int amount = (Integer) getValue("damage"); if (amount > 0) { diff --git a/Mage.Sets/src/mage/cards/e/EssenceSymbiote.java b/Mage.Sets/src/mage/cards/e/EssenceSymbiote.java index 48223c26a6a..32e5b9d6cef 100644 --- a/Mage.Sets/src/mage/cards/e/EssenceSymbiote.java +++ b/Mage.Sets/src/mage/cards/e/EssenceSymbiote.java @@ -4,7 +4,6 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; -import mage.abilities.keyword.MutateAbility; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,9 +13,6 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; import mage.game.events.GameEvent; @@ -26,8 +22,6 @@ import mage.game.events.GameEvent; */ public final class EssenceSymbiote extends CardImpl { - - public EssenceSymbiote(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); @@ -55,24 +49,18 @@ public final class EssenceSymbiote extends CardImpl { class EssenceSymbioteTriggeredAbility extends TriggeredAbilityImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature you control mutates"); - - static { - filter.add(new AbilityPredicate(MutateAbility.class)); - } - - public EssenceSymbioteTriggeredAbility(Zone zone, Effect effect) { + EssenceSymbioteTriggeredAbility(Zone zone, Effect effect) { super(zone, effect, false); setTriggerPhrase("Whenever a creature you control mutates, "); } - public EssenceSymbioteTriggeredAbility(final mage.cards.e.EssenceSymbioteTriggeredAbility ability) { + private EssenceSymbioteTriggeredAbility(final EssenceSymbioteTriggeredAbility ability) { super(ability); } @Override - public mage.cards.e.EssenceSymbioteTriggeredAbility copy() { - return new mage.cards.e.EssenceSymbioteTriggeredAbility(this); + public EssenceSymbioteTriggeredAbility copy() { + return new EssenceSymbioteTriggeredAbility(this); } @Override @@ -85,23 +73,6 @@ class EssenceSymbioteTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { // TODO: Implement this - /* - Permanent sourcePermanent = game.getPermanent(event.getSourceId()); - Permanent targetPermanent = game.getPermanent(event.getTargetId()); - if (sourcePermanent != null && targetPermanent != null) { - Player controller = game.getPlayer(targetPermanent.getControllerId()); - if (controller != null - && event.getTargetId().equals(targetPermanent.getId()) - && controller.getId().equals(sourcePermanent.getControllerId()) - && this.isControlledBy(controller.getId())) { - for (Effect effect : this.getEffects()) { - effect.setBoostedValue("targetId", targetPermanent.getId()); - effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), targetPermanent.getZoneChangeCounter(game))); - } - return true; - } - } - */ return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index 0bee443c02c..45dbd8023c4 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -147,7 +147,7 @@ class EtherealValkyrieEffect extends OneShotEffect { foretellAbility.setControllerId(exileCard.getOwnerId()); game.getState().addOtherAbility(exileCard, foretellAbility); foretellAbility.activate(game, true); - ContinuousEffect effect = foretellAbility.new ForetellAddCostEffect(new MageObjectReference(exileCard, game)); + ContinuousEffect effect = new ForetellAbility.ForetellAddCostEffect(new MageObjectReference(exileCard, game)); game.addEffect(effect, copiedSource); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETOLD, exileCard.getId(), null, null)); } diff --git a/Mage.Sets/src/mage/cards/e/Evangelize.java b/Mage.Sets/src/mage/cards/e/Evangelize.java index 7fc44dd56d1..f4bc78b2de2 100644 --- a/Mage.Sets/src/mage/cards/e/Evangelize.java +++ b/Mage.Sets/src/mage/cards/e/Evangelize.java @@ -6,7 +6,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetOpponentsChoicePermanent; import java.util.UUID; @@ -16,8 +16,6 @@ import java.util.UUID; */ public final class Evangelize extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - public Evangelize(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}"); @@ -28,7 +26,7 @@ public final class Evangelize extends CardImpl { GainControlTargetEffect effect = new GainControlTargetEffect(Duration.EndOfGame); effect.setText("Gain control of target creature of an opponent's choice they control"); this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false)); + this.getSpellAbility().addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE, false)); } private Evangelize(final Evangelize card) { diff --git a/Mage.Sets/src/mage/cards/e/Excavation.java b/Mage.Sets/src/mage/cards/e/Excavation.java index bd1ae8be2c3..f451a92e115 100644 --- a/Mage.Sets/src/mage/cards/e/Excavation.java +++ b/Mage.Sets/src/mage/cards/e/Excavation.java @@ -1,25 +1,17 @@ - package mage.cards.e; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.ActivatedAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledLandPermanent; -import mage.game.Game; -import mage.players.Player; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @@ -46,33 +38,3 @@ public final class Excavation extends CardImpl { return new Excavation(this); } } - -class ExcavationEffect extends OneShotEffect { - - ExcavationEffect() { - super(Outcome.DrawCard); - this.staticText = "Draw a card. Any player may activate this ability"; - } - - private ExcavationEffect(final ExcavationEffect effect) { - super(effect); - } - - @Override - public ExcavationEffect copy() { - return new ExcavationEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - if (source instanceof ActivatedAbilityImpl) { - Player activator = game.getPlayer(((ActivatedAbilityImpl) source).getActivatorId()); - if (activator != null) { - activator.drawCards(1, source, game); - return true; - } - - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/e/Excavator.java b/Mage.Sets/src/mage/cards/e/Excavator.java index c715817f6d2..056e770f9ab 100644 --- a/Mage.Sets/src/mage/cards/e/Excavator.java +++ b/Mage.Sets/src/mage/cards/e/Excavator.java @@ -106,7 +106,7 @@ class ExcavatorEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanentOrLKIBattlefield(permanentId); if (permanent != null) { for(Ability ability : abilities) diff --git a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java index eb1db60b4a2..b41ee9dd0b1 100644 --- a/Mage.Sets/src/mage/cards/e/ExclusionRitual.java +++ b/Mage.Sets/src/mage/cards/e/ExclusionRitual.java @@ -67,7 +67,7 @@ class ExclusionRitualImprintEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (controller != null && sourcePermanent != null && targetPermanent != null) { controller.moveCardToExileWithInfo(targetPermanent, getId(), sourcePermanent.getIdName(), source, game, Zone.BATTLEFIELD, true); diff --git a/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java new file mode 100644 index 00000000000..38a2f905363 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java @@ -0,0 +1,179 @@ +package mage.cards.e; + +import java.util.Set; +import java.util.UUID; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author kleese + */ +public final class ExpeditedInheritance extends CardImpl { + + public ExpeditedInheritance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}{R}"); + + // Whenever a creature is dealt damage, its controller may exile that many cards from the top of their library. They may play those cards until the end of their next turn. + this.addAbility(new ExpeditedInheritanceTriggeredAbility(new ExpeditedInheritanceExileEffect())); + } + + private ExpeditedInheritance(final ExpeditedInheritance card) { + super(card); + } + + @Override + public ExpeditedInheritance copy() { + return new ExpeditedInheritance(this); + } +} + +class ExpeditedInheritanceTriggeredAbility extends TriggeredAbilityImpl { + + static final String IMPULSE_DRAW_AMOUNT_KEY = "playerDamage"; + static final String TRIGGERING_CREATURE_KEY = "triggeringCreature"; + + public ExpeditedInheritanceTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + } + + private ExpeditedInheritanceTriggeredAbility(final ExpeditedInheritanceTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null || !permanent.isCreature(game)) { + return false; + } + getEffects().setValue(IMPULSE_DRAW_AMOUNT_KEY, event.getAmount()); + getEffects().setValue(TRIGGERING_CREATURE_KEY, new MageObjectReference(event.getTargetId(), game)); + return true; + } + + @Override + public String getRule() { + return "Whenever a creature is dealt damage, its controller may exile that many cards from the top of their library. They may play those cards until the end of their next turn."; + } + + @Override + public ExpeditedInheritanceTriggeredAbility copy() { + return new ExpeditedInheritanceTriggeredAbility(this); + } +} + +class ExpeditedInheritanceExileEffect extends OneShotEffect { + + ExpeditedInheritanceExileEffect() { + super(Outcome.Benefit); + staticText = "Exile that many cards from the top of your library. " + + "Until the end of your next turn, you may play those cards."; + } + + private ExpeditedInheritanceExileEffect(final ExpeditedInheritanceExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Integer impulseDrawAmount = (Integer) this.getValue(ExpeditedInheritanceTriggeredAbility.IMPULSE_DRAW_AMOUNT_KEY); + MageObjectReference mor = (MageObjectReference) this.getValue(ExpeditedInheritanceTriggeredAbility.TRIGGERING_CREATURE_KEY); + if (impulseDrawAmount != null && mor != null) { + Permanent creature = mor.getPermanentOrLKIBattlefield(game); + if (creature != null) { + UUID playerId = creature.getControllerId(); + Player player = game.getPlayer(playerId); + String message = impulseDrawAmount > 1 ? + "Exile " + CardUtil.numberToText(impulseDrawAmount) + " cards from the top of your library. Until the end of your next turn, you may play those cards." + : "Exile the top card of your library. Until the end of your next turn, you may play that card."; + if (player != null && player.chooseUse(outcome, message, source, game)) { + Set cards = player.getLibrary().getTopCards(game, impulseDrawAmount); + if (!cards.isEmpty()) { + player.moveCards(cards, Zone.EXILED, source, game); + for (Card card:cards){ + ContinuousEffect effect = new ExpeditedInheritanceMayPlayEffect(playerId); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + } + } + } + return true; + } + return false; + } + + @Override + public ExpeditedInheritanceExileEffect copy() { + return new ExpeditedInheritanceExileEffect(this); + } +} + +class ExpeditedInheritanceMayPlayEffect extends AsThoughEffectImpl { + + private int triggeredOnTurn = 0; + private final UUID cardOwnerId; + + ExpeditedInheritanceMayPlayEffect(UUID playerId) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.staticText = "Until the end of your next turn, you may play this card."; + this.cardOwnerId = playerId; + } + + private ExpeditedInheritanceMayPlayEffect(final ExpeditedInheritanceMayPlayEffect effect) { + super(effect); + triggeredOnTurn = effect.triggeredOnTurn; + cardOwnerId = effect.cardOwnerId; + } + + @Override + public ExpeditedInheritanceMayPlayEffect copy() { + return new ExpeditedInheritanceMayPlayEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + triggeredOnTurn = game.getTurnNum(); + } + + @Override + public boolean isInactive(Ability source, Game game) { + return triggeredOnTurn != game.getTurnNum() + && game.getPhase().getStep().getType() == PhaseStep.END_TURN + && game.isActivePlayer(cardOwnerId); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + UUID objectIdToCast = CardUtil.getMainCardId(game, sourceId); + return cardOwnerId != null && cardOwnerId.equals(affectedControllerId) + && getTargetPointer().getTargets(game, source).contains(objectIdToCast); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExperimentTwelve.java b/Mage.Sets/src/mage/cards/e/ExperimentTwelve.java new file mode 100644 index 00000000000..cfc9056583d --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExperimentTwelve.java @@ -0,0 +1,58 @@ +package mage.cards.e; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.TurnedFaceUpAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.TargetPermanentPowerCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.constants.SubType; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterPermanentThisOrAnother; +import mage.filter.StaticFilters; + +/** + * @author Cguy7777 + */ +public final class ExperimentTwelve extends CardImpl { + + private static final FilterPermanentThisOrAnother filter = new FilterPermanentThisOrAnother(StaticFilters.FILTER_CONTROLLED_CREATURE, true); + + public ExperimentTwelve(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Experiment Twelve or another creature you control is turned face up, put +1/+1 counters on that creature equal to its power. + Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(0), TargetPermanentPowerCount.instance) + .setText("put +1/+1 counters on that creature equal to its power"); + this.addAbility(new TurnedFaceUpAllTriggeredAbility(effect, filter, true)); + + // Disguise {4}{G} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{4}{G}"))); + + } + + private ExperimentTwelve(final ExperimentTwelve card) { + super(card); + } + + @Override + public ExperimentTwelve copy() { + return new ExperimentTwelve(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java b/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java new file mode 100644 index 00000000000..629216547b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java @@ -0,0 +1,95 @@ +package mage.cards.e; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +/** + * @author Cguy7777 + */ +public final class ExpertLevelSafe extends CardImpl { + + public ExpertLevelSafe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // When Expert-Level Safe enters the battlefield, exile the top two cards of your library face down. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new ExileCardsFromTopOfLibraryControllerEffect(2, true, true))); + + // {1}, {T}: You and target opponent each secretly choose 1, 2, or 3. Then those choices are revealed. + // If they match, sacrifice Expert-Level Safe and put all cards exiled with it into their owners' hands. + // Otherwise, exile the top card of your library face down. + Ability ability = new SimpleActivatedAbility(new ExpertLevelSafeEffect(), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private ExpertLevelSafe(final ExpertLevelSafe card) { + super(card); + } + + @Override + public ExpertLevelSafe copy() { + return new ExpertLevelSafe(this); + } +} + +class ExpertLevelSafeEffect extends OneShotEffect { + + ExpertLevelSafeEffect() { + super(Outcome.Benefit); + staticText = "you and target opponent each secretly choose 1, 2, or 3. Then those choices are revealed. " + + "If they match, sacrifice {this} and put all cards exiled with it into their owners' hands. " + + "Otherwise, exile the top card of your library face down"; + } + + protected ExpertLevelSafeEffect(ExpertLevelSafeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(source.getFirstTarget()); + if (controller == null || opponent == null) { + return false; + } + + int controllerChoice = controller.getAmount(1, 3, "Choose a number", game); + int opponentChoice = opponent.getAmount(1, 3, "Choose a number", game); + + game.informPlayers(controller.getLogName() + " chose " + controllerChoice); + game.informPlayers(opponent.getLogName() + " chose " + opponentChoice); + + if (controllerChoice == opponentChoice) { + new SacrificeSourceEffect().apply(game, source); + new ReturnFromExileForSourceEffect(Zone.HAND).apply(game, source); + return true; + } + + new ExileCardsFromTopOfLibraryControllerEffect(1, true, true).apply(game, source); + return true; + } + + @Override + public ExpertLevelSafeEffect copy() { + return new ExpertLevelSafeEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java b/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java index 470a3b6241a..de320db2082 100644 --- a/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java +++ b/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java @@ -78,7 +78,7 @@ class ExplosiveRevelationEffect extends OneShotEffect { // the nonland card int damage = nonLandCard == null ? 0 : nonLandCard.getManaValue(); // assign damage to target - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent targetedCreature = game.getPermanent(targetId); if (targetedCreature != null) { targetedCreature.damage(damage, source.getSourceId(), source, game, false, true); diff --git a/Mage.Sets/src/mage/cards/e/ExtricatorOfFlesh.java b/Mage.Sets/src/mage/cards/e/ExtricatorOfFlesh.java index 859aef869f3..96aba193e73 100644 --- a/Mage.Sets/src/mage/cards/e/ExtricatorOfFlesh.java +++ b/Mage.Sets/src/mage/cards/e/ExtricatorOfFlesh.java @@ -1,4 +1,3 @@ - package mage.cards.e; import java.util.UUID; @@ -19,6 +18,7 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; import mage.game.permanent.token.EldraziHorrorToken; @@ -29,7 +29,7 @@ import mage.game.permanent.token.EldraziHorrorToken; public final class ExtricatorOfFlesh extends CardImpl { private static final FilterControlledCreaturePermanent filterNonEldrazi = new FilterControlledCreaturePermanent("non-Eldrazi creature"); - private static final FilterControlledCreaturePermanent filterEldrazi = new FilterControlledCreaturePermanent(SubType.ELDRAZI, "Eldrazi"); + private static final FilterControlledPermanent filterEldrazi = new FilterControlledPermanent(SubType.ELDRAZI, "Eldrazi"); static { filterNonEldrazi.add(Predicates.not(SubType.ELDRAZI.getPredicate())); diff --git a/Mage.Sets/src/mage/cards/e/EyeForAnEye.java b/Mage.Sets/src/mage/cards/e/EyeForAnEye.java index 191592d95a5..e3cac0359e9 100644 --- a/Mage.Sets/src/mage/cards/e/EyeForAnEye.java +++ b/Mage.Sets/src/mage/cards/e/EyeForAnEye.java @@ -59,8 +59,8 @@ class EyeForAnEyeEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { - this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java b/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java index 1ee099c866a..01e61c0f9fd 100644 --- a/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java +++ b/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java @@ -97,10 +97,10 @@ class EyeOfTheStormEffect1 extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); boolean noLongerOnStack = false;// spell was exiled already by another effect, for example NivMagus Elemental if (spell == null) { - spell = ((Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK)); + spell = ((Spell) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK)); noLongerOnStack = true; } Permanent eyeOfTheStorm = game.getPermanentOrLKIBattlefield(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/e/EzioBladeOfVengeance.java b/Mage.Sets/src/mage/cards/e/EzioBladeOfVengeance.java new file mode 100644 index 00000000000..3908b5f2b0b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EzioBladeOfVengeance.java @@ -0,0 +1,53 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EzioBladeOfVengeance extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent(SubType.ASSASSIN, "an Assassin you control"); + + public EzioBladeOfVengeance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever an Assassin you control deals combat damage to a player, draw a card. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter, + false, SetTargetPointer.NONE, true + )); + } + + private EzioBladeOfVengeance(final EzioBladeOfVengeance card) { + super(card); + } + + @Override + public EzioBladeOfVengeance copy() { + return new EzioBladeOfVengeance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FainTheBroker.java b/Mage.Sets/src/mage/cards/f/FainTheBroker.java index 0b36a02111c..eacebc2862d 100644 --- a/Mage.Sets/src/mage/cards/f/FainTheBroker.java +++ b/Mage.Sets/src/mage/cards/f/FainTheBroker.java @@ -20,11 +20,9 @@ import mage.filter.StaticFilters; import mage.game.permanent.token.InklingToken; import mage.game.permanent.token.TreasureToken; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; -import mage.filter.common.FilterControlledCreaturePermanent; /** * @author TheElk801 @@ -50,7 +48,7 @@ public final class FainTheBroker extends CardImpl { // {T}, Remove a counter from a creature you control: Create a Treasure token. ability = new SimpleActivatedAbility(new CreateTokenEffect(new TreasureToken()), new TapSourceCost()); - ability.addCost(new RemoveCounterCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true))); + ability.addCost(new RemoveCounterCost(new TargetControlledCreaturePermanent().withNotTarget(true))); this.addAbility(ability); // {T}, Sacrifice an artifact: Create a 2/1 white and black Inkling creature token with flying. diff --git a/Mage.Sets/src/mage/cards/f/FaithfulSquire.java b/Mage.Sets/src/mage/cards/f/FaithfulSquire.java index 089f77078f9..6c0cbeacbdf 100644 --- a/Mage.Sets/src/mage/cards/f/FaithfulSquire.java +++ b/Mage.Sets/src/mage/cards/f/FaithfulSquire.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.cards.f; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/f/FalcoSparaPactweaver.java b/Mage.Sets/src/mage/cards/f/FalcoSparaPactweaver.java index d85c8983756..a174f339d9e 100644 --- a/Mage.Sets/src/mage/cards/f/FalcoSparaPactweaver.java +++ b/Mage.Sets/src/mage/cards/f/FalcoSparaPactweaver.java @@ -19,7 +19,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; @@ -115,7 +114,7 @@ class FalcoSparaPactweaverEffect extends AsThoughEffectImpl { } Costs newCosts = new CostsImpl<>(); - newCosts.add(new RemoveCounterCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true))); + newCosts.add(new RemoveCounterCost(new TargetControlledCreaturePermanent().withNotTarget(true))); newCosts.addAll(cardToCheck.getSpellAbility().getCosts()); player.setCastSourceIdWithAlternateMana( cardToCheck.getId(), cardToCheck.getManaCost(), newCosts, diff --git a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java index 55c252915be..83646f7a6ed 100644 --- a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java +++ b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java @@ -31,7 +31,7 @@ public final class FallOfCairAndros extends CardImpl { this.addAbility(new FallOfCairAndrosTriggeredAbility()); // {7}{R}: Fall of Cair Andros deals 7 damage to target creature. - Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(7), new ManaCostsImpl("{7}{R}")); + Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(7), new ManaCostsImpl<>("{7}{R}")); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/FallajiWayfarer.java b/Mage.Sets/src/mage/cards/f/FallajiWayfarer.java index 7df1a850de1..6c405e5d232 100644 --- a/Mage.Sets/src/mage/cards/f/FallajiWayfarer.java +++ b/Mage.Sets/src/mage/cards/f/FallajiWayfarer.java @@ -10,8 +10,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; import mage.filter.FilterMana; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.MulticoloredPredicate; @@ -23,11 +23,10 @@ import java.util.UUID; */ public final class FallajiWayfarer extends CardImpl { - private static final FilterCard filter = new FilterCard("multicolored spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("multicolored spells you cast"); static { filter.add(MulticoloredPredicate.instance); - filter.add(Predicates.not(CardType.LAND.getPredicate())); filter.add(Predicates.not(new AbilityPredicate(ConvokeAbility.class))); // So there are not redundant copies being added to each card } diff --git a/Mage.Sets/src/mage/cards/f/FallenShinobi.java b/Mage.Sets/src/mage/cards/f/FallenShinobi.java index bf9a7720880..2215e405298 100644 --- a/Mage.Sets/src/mage/cards/f/FallenShinobi.java +++ b/Mage.Sets/src/mage/cards/f/FallenShinobi.java @@ -66,7 +66,7 @@ class FallenShinobiEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index f189e992428..587edda9082 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -14,7 +14,7 @@ import mage.constants.PhaseStep; import mage.filter.FilterPermanent; import mage.filter.common.FilterAttackingCreature; import mage.filter.predicate.permanent.DefendingPlayerControlsNoSourcePredicate; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.BlockerDeclaredEvent; @@ -125,7 +125,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect { return false; } FilterAttackingCreature filter = new FilterAttackingCreature("creature attacking " + targetsController.getLogName()); - filter.add(new PermanentInListPredicate(list)); + filter.add(new PermanentReferenceInCollectionPredicate(list, game)); TargetPermanent target = new TargetPermanent(filter); target.withNotTarget(true); if (target.canChoose(controller.getId(), source, game)) { diff --git a/Mage.Sets/src/mage/cards/f/FamiliarGround.java b/Mage.Sets/src/mage/cards/f/FamiliarGround.java index 55113b15d32..749958e8e86 100644 --- a/Mage.Sets/src/mage/cards/f/FamiliarGround.java +++ b/Mage.Sets/src/mage/cards/f/FamiliarGround.java @@ -8,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -21,7 +21,7 @@ public final class FamiliarGround extends CardImpl { // Each creature you control can't be blocked by more than one creature. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeBlockedByMoreThanOneAllEffect(new FilterControlledCreaturePermanent()))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeBlockedByMoreThanOneAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE))); } private FamiliarGround(final FamiliarGround card) { diff --git a/Mage.Sets/src/mage/cards/f/FatedReturn.java b/Mage.Sets/src/mage/cards/f/FatedReturn.java index 17ef52dd54e..0b14a823d87 100644 --- a/Mage.Sets/src/mage/cards/f/FatedReturn.java +++ b/Mage.Sets/src/mage/cards/f/FatedReturn.java @@ -11,6 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -27,7 +28,7 @@ public final class FatedReturn extends CardImpl { // Put target creature card from a graveyard onto the battlefield under your control. It gains indestructible. If it's your turn, scry 2. this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.Custom, "It gains indestructible")); this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new ScryEffect(2), MyTurnCondition.instance, diff --git a/Mage.Sets/src/mage/cards/f/FathomTrawl.java b/Mage.Sets/src/mage/cards/f/FathomTrawl.java index 523d62d71e9..ea48e7f553e 100644 --- a/Mage.Sets/src/mage/cards/f/FathomTrawl.java +++ b/Mage.Sets/src/mage/cards/f/FathomTrawl.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -34,51 +33,54 @@ public final class FathomTrawl extends CardImpl { return new FathomTrawl(this); } - class FathomTrawlEffect extends OneShotEffect { +} - public FathomTrawlEffect() { - super(Outcome.DrawCard); - this.staticText = "Reveal cards from the top of your library until you reveal three nonland cards. Put the nonland cards revealed this way into your hand, then put the rest of the revealed cards on the bottom of your library in any order"; +class FathomTrawlEffect extends OneShotEffect { + + FathomTrawlEffect() { + super(Outcome.DrawCard); + this.staticText = "Reveal cards from the top of your library until you reveal three nonland cards. " + + "Put the nonland cards revealed this way into your hand, then put the rest of the revealed " + + "cards on the bottom of your library in any order"; + } + + private FathomTrawlEffect(final FathomTrawlEffect effect) { + super(effect); + } + + @Override + public FathomTrawlEffect copy() { + return new FathomTrawlEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = game.getObject(source); + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || sourceObject == null) { + return false; } - - private FathomTrawlEffect(final FathomTrawlEffect effect) { - super(effect); - } - - @Override - public FathomTrawlEffect copy() { - return new FathomTrawlEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - MageObject sourceObject = game.getObject(source); - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null || sourceObject == null) { - return false; - } - Cards cards = new CardsImpl(); - Cards nonlandCards = new CardsImpl(); - Cards landCards = new CardsImpl(); - for (Card card : controller.getLibrary().getCards(game)) { - if (card != null) { - cards.add(card); - if (!card.isLand(game)) { - nonlandCards.add(card); - if (nonlandCards.size() == 3) { - break; - } - } else { - landCards.add(card); + Cards cards = new CardsImpl(); + Cards nonlandCards = new CardsImpl(); + Cards landCards = new CardsImpl(); + for (Card card : controller.getLibrary().getCards(game)) { + if (card != null) { + cards.add(card); + if (!card.isLand(game)) { + nonlandCards.add(card); + if (nonlandCards.size() == 3) { + break; } } else { - break; + landCards.add(card); } + } else { + break; } - controller.revealCards(sourceObject.getName(), cards, game); - controller.moveCards(nonlandCards, Zone.HAND, source, game); - controller.putCardsOnBottomOfLibrary(landCards, game, source, true); - return true; } + controller.revealCards(sourceObject.getName(), cards, game); + controller.moveCards(nonlandCards, Zone.HAND, source, game); + controller.putCardsOnBottomOfLibrary(landCards, game, source, true); + return true; } } diff --git a/Mage.Sets/src/mage/cards/f/FeastOfBlood.java b/Mage.Sets/src/mage/cards/f/FeastOfBlood.java index bc41d9da3c6..482057d215b 100644 --- a/Mage.Sets/src/mage/cards/f/FeastOfBlood.java +++ b/Mage.Sets/src/mage/cards/f/FeastOfBlood.java @@ -11,7 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -26,7 +26,7 @@ public final class FeastOfBlood extends CardImpl { // Cast Feast of Blood only if you control two or more Vampires. this.addAbility(new CastOnlyIfConditionIsTrueAbility( new PermanentsOnTheBattlefieldCondition( - new FilterControlledCreaturePermanent(SubType.VAMPIRE, "you control two or more Vampires"), + new FilterControlledPermanent(SubType.VAMPIRE, "you control two or more Vampires"), ComparisonType.MORE_THAN, 1))); // Destroy target creature. You gain 4 life. diff --git a/Mage.Sets/src/mage/cards/f/Fecundity.java b/Mage.Sets/src/mage/cards/f/Fecundity.java index 97a9283deb8..92455435f31 100644 --- a/Mage.Sets/src/mage/cards/f/Fecundity.java +++ b/Mage.Sets/src/mage/cards/f/Fecundity.java @@ -54,9 +54,9 @@ class FecundityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = (Permanent) game.getLastKnownInformation(this.getTargetPointer() - // Card can be moved again (e.g. commander replacement) so we need the row id from fixed target to check - .getFixedTarget(game, source).getTarget(), Zone.BATTLEFIELD); + // card can be moved again (e.g. commander replacement) so we need the row id from fixed target to check + // TODO: bugged with commander replacement effects? + Permanent permanent = (Permanent) game.getLastKnownInformation(this.getTargetPointer().getFirst(game, source), Zone.BATTLEFIELD); if (permanent != null) { Player controller = game.getPlayer(permanent.getControllerId()); if (controller != null) { diff --git a/Mage.Sets/src/mage/cards/f/FelhideSpiritbinder.java b/Mage.Sets/src/mage/cards/f/FelhideSpiritbinder.java index 4b0f5148fa4..7cdd4a7a6ca 100644 --- a/Mage.Sets/src/mage/cards/f/FelhideSpiritbinder.java +++ b/Mage.Sets/src/mage/cards/f/FelhideSpiritbinder.java @@ -74,7 +74,7 @@ class FelhideSpiritbinderEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, CardType.ENCHANTMENT, true); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect.apply(game, source)) { for (Permanent tokenPermanent : effect.getAddedPermanents()) { ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/f/FellBeastsShriek.java b/Mage.Sets/src/mage/cards/f/FellBeastsShriek.java index 331dabf1d8b..f6f3441c349 100644 --- a/Mage.Sets/src/mage/cards/f/FellBeastsShriek.java +++ b/Mage.Sets/src/mage/cards/f/FellBeastsShriek.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -66,30 +66,26 @@ class FellBeastsShriekEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - List creaturesChosen = new ArrayList<>(); - - // For each opponent, get the creature to tap+goad - for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { - if (controller.hasOpponent(playerId, game)) { - Player opponent = game.getPlayer(playerId); - if (opponent != null) { - TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); - target.withNotTarget(true); - if (opponent.choose(Outcome.Detriment, target, source, game)) { - creaturesChosen.add(game.getPermanent(target.getTargets().get(0))); - } - } + if (controller == null) { + return false; + } + List creaturesChosen = new ArrayList<>(); + // For each opponent, get the creature to tap+goad + for (UUID playerId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null) { + TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); + target.withNotTarget(true); + if (opponent.choose(Outcome.Detriment, target, source, game)) { + creaturesChosen.add(game.getPermanent(target.getTargets().get(0))); } } - - FilterPermanent filter = new FilterCreaturePermanent(); - filter.add(new PermanentInListPredicate(creaturesChosen)); - new TapAllEffect(filter).apply(game, source); - ContinuousEffect goadEffect = new GoadAllEffect(filter); - game.addEffect(goadEffect, source); - return true; } - return false; + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(new PermanentReferenceInCollectionPredicate(creaturesChosen, game)); + new TapAllEffect(filter).apply(game, source); + ContinuousEffect goadEffect = new GoadAllEffect(filter); + game.addEffect(goadEffect, source); + return true; } } diff --git a/Mage.Sets/src/mage/cards/f/FerrousLake.java b/Mage.Sets/src/mage/cards/f/FerrousLake.java new file mode 100644 index 00000000000..1dd51bb0126 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FerrousLake.java @@ -0,0 +1,37 @@ +package mage.cards.f; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FerrousLake extends CardImpl { + + public FerrousLake(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {1}, {T}: Add {U}{R}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new Mana(0, 1, 0, 1, 0, 0, 0, 0), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private FerrousLake(final FerrousLake card) { + super(card); + } + + @Override + public FerrousLake copy() { + return new FerrousLake(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FickleEfreet.java b/Mage.Sets/src/mage/cards/f/FickleEfreet.java index 0eeb0bd5643..0d0326ceb10 100644 --- a/Mage.Sets/src/mage/cards/f/FickleEfreet.java +++ b/Mage.Sets/src/mage/cards/f/FickleEfreet.java @@ -1,15 +1,12 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.common.AttacksOrBlocksTriggeredAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -20,6 +17,8 @@ import mage.target.Target; import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author L_J @@ -67,60 +66,22 @@ class FickleEfreetChangeControlEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (controller != null) { - if (!controller.flipCoin(source, game, true)) { - if (sourcePermanent != null) { - Target target = new TargetOpponent(true); - if (target.canChoose(controller.getId(), source, game)) { - while (!target.isChosen() && target.canChoose(controller.getId(), source, game) && controller.canRespond()) { - controller.chooseTarget(outcome, target, source, game); - } - } - Player chosenOpponent = game.getPlayer(target.getFirstTarget()); - if (chosenOpponent != null) { - ContinuousEffect effect = new FickleEfreetGainControlEffect(Duration.Custom, target.getFirstTarget()); - effect.setTargetPointer(new FixedTarget(sourcePermanent.getId(), game)); - game.addEffect(effect, source); - game.informPlayers(chosenOpponent.getLogName() + " has gained control of " + sourcePermanent.getLogName()); - return true; - } - } + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (controller == null || sourcePermanent == null) { + return false; + } + if (!controller.flipCoin(source, game, true)) { + Target target = new TargetOpponent(true); + controller.chooseTarget(outcome, target, source, game); + Player chosenOpponent = game.getPlayer(target.getFirstTarget()); + if (chosenOpponent != null) { + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, chosenOpponent.getId() + ).setTargetPointer(new FixedTarget(sourcePermanent, game)), source); + game.informPlayers(chosenOpponent.getLogName() + " has gained control of " + sourcePermanent.getLogName()); + return true; } } return false; } } - -class FickleEfreetGainControlEffect extends ContinuousEffectImpl { - - UUID controller; - - public FickleEfreetGainControlEffect(Duration duration, UUID controller) { - super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - this.controller = controller; - this.staticText = "That player gains control of {this}"; - } - - private FickleEfreetGainControlEffect(final FickleEfreetGainControlEffect effect) { - super(effect); - this.controller = effect.controller; - } - - @Override - public FickleEfreetGainControlEffect copy() { - return new FickleEfreetGainControlEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (targetPointer != null) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); - } - if (permanent != null) { - return permanent.changeControllerId(controller, game, source); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FiendOfTheShadows.java b/Mage.Sets/src/mage/cards/f/FiendOfTheShadows.java index 9a5bf20e7e9..b045bd9d4fb 100644 --- a/Mage.Sets/src/mage/cards/f/FiendOfTheShadows.java +++ b/Mage.Sets/src/mage/cards/f/FiendOfTheShadows.java @@ -75,7 +75,7 @@ class FiendOfTheShadowsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null || player.getHand().isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/f/FinalWordPhantom.java b/Mage.Sets/src/mage/cards/f/FinalWordPhantom.java new file mode 100644 index 00000000000..2fa3880e2d5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinalWordPhantom.java @@ -0,0 +1,59 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.IsStepCondition; +import mage.abilities.condition.common.OnOpponentsTurnCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterNonlandCard; + +/** + * @author Cguy7777 + */ +public final class FinalWordPhantom extends CardImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard("spells"); + + public FinalWordPhantom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // During each opponent's end step, you may cast spells as though they had flash. + Condition condition = new CompoundCondition(OnOpponentsTurnCondition.instance, new IsStepCondition(PhaseStep.END_TURN, false)); + this.addAbility(new SimpleStaticAbility( + new ConditionalAsThoughEffect(new CastAsThoughItHadFlashAllEffect(Duration.WhileOnBattlefield, filter), condition) + .setText("during each opponent's end step, you may cast spells as though they had flash"))); + } + + private FinalWordPhantom(final FinalWordPhantom card) { + super(card); + } + + @Override + public FinalWordPhantom copy() { + return new FinalWordPhantom(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/Fireball.java b/Mage.Sets/src/mage/cards/f/Fireball.java index 62e5287ad5e..db7580636dd 100644 --- a/Mage.Sets/src/mage/cards/f/Fireball.java +++ b/Mage.Sets/src/mage/cards/f/Fireball.java @@ -66,12 +66,12 @@ class FireballEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - int numTargets = targetPointer.getTargets(game, source).size(); + int numTargets = getTargetPointer().getTargets(game, source).size(); int damage = source.getManaCostsToPay().getX(); if (numTargets > 0) { int damagePer = damage / numTargets; if (damagePer > 0) { - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetId); if (permanent != null) { permanent.damage(damagePer, source.getSourceId(), source, game, false, true); diff --git a/Mage.Sets/src/mage/cards/f/FirefistAdept.java b/Mage.Sets/src/mage/cards/f/FirefistAdept.java index b83bbec29c8..adc4a804398 100644 --- a/Mage.Sets/src/mage/cards/f/FirefistAdept.java +++ b/Mage.Sets/src/mage/cards/f/FirefistAdept.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -11,7 +10,7 @@ import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetOpponentsCreaturePermanent; /** @@ -20,7 +19,7 @@ import mage.target.common.TargetOpponentsCreaturePermanent; */ public final class FirefistAdept extends CardImpl { - private static final FilterControlledCreaturePermanent filterCount = new FilterControlledCreaturePermanent("Wizards you control"); + private static final FilterControlledPermanent filterCount = new FilterControlledPermanent("Wizards you control"); static { filterCount.add(SubType.WIZARD.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/f/FiresongAndSunspeaker.java b/Mage.Sets/src/mage/cards/f/FiresongAndSunspeaker.java index 7eafe7f527a..9966bf2722e 100644 --- a/Mage.Sets/src/mage/cards/f/FiresongAndSunspeaker.java +++ b/Mage.Sets/src/mage/cards/f/FiresongAndSunspeaker.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -15,7 +14,7 @@ import mage.abilities.keyword.LifelinkAbility; import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; @@ -28,7 +27,7 @@ import mage.target.common.TargetCreatureOrPlayer; */ public final class FiresongAndSunspeaker extends CardImpl { - private static final FilterCard filter = new FilterCard("red instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("red instant and sorcery spells you control"); static { filter.add(new ColorPredicate(ObjectColor.RED)); @@ -99,6 +98,6 @@ class FiresongAndSunspeakerTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a white instant or sorcery spell causes you to gain life, Firesong and Sunspeaker deals 3 damage to target creature or player."; + return "Whenever a white instant or sorcery spell causes you to gain life, {this} deals 3 damage to target creature or player."; } } diff --git a/Mage.Sets/src/mage/cards/f/FlameKinWarScout.java b/Mage.Sets/src/mage/cards/f/FlameKinWarScout.java index 657891e617d..98976535d90 100644 --- a/Mage.Sets/src/mage/cards/f/FlameKinWarScout.java +++ b/Mage.Sets/src/mage/cards/f/FlameKinWarScout.java @@ -70,7 +70,7 @@ class FlameKinWarScourEffect extends OneShotEffect { if (permanent != null) { if (permanent.sacrifice(source, game)) { Effect effect = new DamageTargetEffect(4).setText("{this} deals 4 damage to it"); - effect.setTargetPointer(this.getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/f/FlamekinHerald.java b/Mage.Sets/src/mage/cards/f/FlamekinHerald.java index 07bb4023070..3536cdd674f 100644 --- a/Mage.Sets/src/mage/cards/f/FlamekinHerald.java +++ b/Mage.Sets/src/mage/cards/f/FlamekinHerald.java @@ -8,8 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.predicate.Predicates; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.mageobject.CommanderPredicate; import java.util.UUID; @@ -19,11 +18,10 @@ import java.util.UUID; */ public final class FlamekinHerald extends CardImpl { - private static final FilterCard filter = new FilterCard("Commander spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("Commander spells you cast"); static { filter.add(CommanderPredicate.instance); - filter.add(Predicates.not(CardType.LAND.getPredicate())); } public FlamekinHerald(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/f/FlameshadowConjuring.java b/Mage.Sets/src/mage/cards/f/FlameshadowConjuring.java index db60f72da53..8c4c36677f3 100644 --- a/Mage.Sets/src/mage/cards/f/FlameshadowConjuring.java +++ b/Mage.Sets/src/mage/cards/f/FlameshadowConjuring.java @@ -69,7 +69,7 @@ class FlameshadowConjuringEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, null, true); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect.apply(game, source)) { for (Permanent tokenPermanent : effect.getAddedPermanents()) { ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/f/FlareOfCultivation.java b/Mage.Sets/src/mage/cards/f/FlareOfCultivation.java new file mode 100644 index 00000000000..a69263f3436 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlareOfCultivation.java @@ -0,0 +1,51 @@ +package mage.cards.f; + +import mage.ObjectColor; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlareOfCultivation extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("nontoken green creature"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(new ColorPredicate(ObjectColor.GREEN)); + } + + public FlareOfCultivation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}{G}"); + + // You may sacrifice a nontoken green creature rather than pay this spell's mana cost. + this.addAbility(new AlternativeCostSourceAbility(new SacrificeTargetCost(filter)).setRuleAtTheTop(true)); + + // Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect( + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS) + )); + } + + private FlareOfCultivation(final FlareOfCultivation card) { + super(card); + } + + @Override + public FlareOfCultivation copy() { + return new FlareOfCultivation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FleetingMemories.java b/Mage.Sets/src/mage/cards/f/FleetingMemories.java index 1a8009946da..4ad98ad0902 100644 --- a/Mage.Sets/src/mage/cards/f/FleetingMemories.java +++ b/Mage.Sets/src/mage/cards/f/FleetingMemories.java @@ -8,8 +8,7 @@ import mage.abilities.effects.keyword.InvestigateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.target.TargetPlayer; import java.util.UUID; @@ -20,12 +19,6 @@ import java.util.UUID; */ public final class FleetingMemories extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("a Clue"); - - static { - filter.add(SubType.CLUE.getPredicate()); - } - public FleetingMemories(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); @@ -33,7 +26,7 @@ public final class FleetingMemories extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new InvestigateEffect(), false)); // Whenever you sacrifice a Clue, target player puts the top three cards of their graveyard into their graveyard. - Ability ability = new SacrificePermanentTriggeredAbility(new MillCardsTargetEffect(3), filter); + Ability ability = new SacrificePermanentTriggeredAbility(new MillCardsTargetEffect(3), StaticFilters.FILTER_CONTROLLED_CLUE); ability.addTarget(new TargetPlayer()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/FlotsamJetsam.java b/Mage.Sets/src/mage/cards/f/FlotsamJetsam.java new file mode 100644 index 00000000000..e99e79682c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlotsamJetsam.java @@ -0,0 +1,117 @@ +package mage.cards.f; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.MillCardsEachPlayerEffect; +import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.cards.SplitCard; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SpellAbilityType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInOpponentsGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class FlotsamJetsam extends SplitCard { + public FlotsamJetsam(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, new CardType[]{CardType.SORCERY}, "{1}{G/U}", "{4}{U/B}{U/B}", SpellAbilityType.SPLIT); + + // Flotsam + // Mill three cards. Investigate. + getLeftHalfCard().getSpellAbility().addEffect(new MillCardsControllerEffect(3)); + getLeftHalfCard().getSpellAbility().addEffect(new InvestigateEffect()); + + // Jetsam + // Each opponent mills three cards, then you may cast a spell from each opponent's graveyard without paying its mana cost. If a spell cast this way would be put into a graveyard, exile it instead. + getRightHalfCard().getSpellAbility().addEffect(new MillCardsEachPlayerEffect(3, TargetController.OPPONENT)); + getRightHalfCard().getSpellAbility().addEffect(new JetsamEffect().concatBy(", then")); + + } + + private FlotsamJetsam(final FlotsamJetsam card) { + super(card); + } + + @Override + public FlotsamJetsam copy() { + return new FlotsamJetsam(this); + } +} + +class JetsamEffect extends OneShotEffect { + + JetsamEffect() { + super(Outcome.PlayForFree); + this.staticText = "you may cast a spell from each opponent's graveyard without paying its mana cost. " + + "If a spell cast this way would be put into a graveyard, exile it instead."; + } + + private JetsamEffect(final JetsamEffect effect) { + super(effect); + } + + @Override + public JetsamEffect copy() { + return new JetsamEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Set opponents = game.getOpponents(source.getControllerId()) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (controller == null || opponents.isEmpty()) { + return false; + } + + CardsImpl cards = new CardsImpl(); + for (Player opponent : opponents) { + if (opponent.getGraveyard().count(StaticFilters.FILTER_CARD_NON_LAND, game) > 0) { + TargetCard target = new TargetCardInOpponentsGraveyard(0, 1, StaticFilters.FILTER_CARD_NON_LAND, true); + target.withNotTarget(true); + if (controller.chooseTarget(outcome, opponent.getGraveyard(), target, source, game)) { + cards.add(game.getCard(target.getFirstTarget())); + } + } + } + + if (!cards.isEmpty()) { + cards + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .map(card -> new FixedTarget(card, game)) + .forEach(target -> { + ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(false); + effect.setTargetPointer(target); + game.addEffect(effect, source); + }); + + CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, StaticFilters.FILTER_CARD_NON_LAND); + } + + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlourishingBloomKin.java b/Mage.Sets/src/mage/cards/f/FlourishingBloomKin.java new file mode 100644 index 00000000000..66aa3e6824d --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlourishingBloomKin.java @@ -0,0 +1,71 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCardInLibrary; + +/** + * @author Cguy7777 + */ +public final class FlourishingBloomKin extends CardImpl { + + private static final FilterControlledPermanent filterForests = new FilterControlledPermanent(SubType.FOREST); + private static final FilterCard filterForestCards = new FilterCard("Forest cards"); + + static { + filterForestCards.add(SubType.FOREST.getPredicate()); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filterForests); + private static final Hint hint = new ValueHint("Forests you control", xValue); + + public FlourishingBloomKin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Flourishing Bloom-Kin gets +1/+1 for each Forest you control. + Ability ability = new SimpleStaticAbility(new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield)); + this.addAbility(ability.addHint(hint)); + + // Disguise {4}{G} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{4}{G}"))); + + // When Flourishing Bloom-Kin is turned face up, search your library for up to two Forest cards and reveal them. + // Put one of them onto the battlefield tapped and the other into your hand, then shuffle. + this.addAbility(new TurnedFaceUpSourceTriggeredAbility( + new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(new TargetCardInLibrary(0, 2, filterForestCards)) + .setText("search your library for up to two Forest cards and reveal them. Put one of them onto the battlefield tapped and the other into your hand, then shuffle"))); + } + + private FlourishingBloomKin(final FlourishingBloomKin card) { + super(card); + } + + @Override + public FlourishingBloomKin copy() { + return new FlourishingBloomKin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FollowTheBodies.java b/Mage.Sets/src/mage/cards/f/FollowTheBodies.java new file mode 100644 index 00000000000..0dd15afbf63 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FollowTheBodies.java @@ -0,0 +1,34 @@ +package mage.cards.f; + +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.GravestormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class FollowTheBodies extends CardImpl { + + public FollowTheBodies(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Gravestorm + this.addAbility(new GravestormAbility()); + + // Investigate + this.getSpellAbility().addEffect(new InvestigateEffect()); + } + + private FollowTheBodies(final FollowTheBodies card) { + super(card); + } + + @Override + public FollowTheBodies copy() { + return new FollowTheBodies(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForensicGadgeteer.java b/Mage.Sets/src/mage/cards/f/ForensicGadgeteer.java new file mode 100644 index 00000000000..43dd95d33d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForensicGadgeteer.java @@ -0,0 +1,95 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class ForensicGadgeteer extends CardImpl { + + public ForensicGadgeteer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.subtype.add(SubType.VEDALKEN, SubType.ARTIFICER, SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever you cast an artifact spell, investigate. + this.addAbility(new SpellCastControllerTriggeredAbility(new InvestigateEffect(), StaticFilters.FILTER_SPELL_AN_ARTIFACT, false)); + + // Activated abilities of artifacts you control cost {1} less to activate. This effect can't reduce the mana in that cost to less than one mana. + this.addAbility(new SimpleStaticAbility(new ForensicGadgeteerEffect())); + } + + private ForensicGadgeteer(final ForensicGadgeteer card) { + super(card); + } + + @Override + public ForensicGadgeteer copy() { + return new ForensicGadgeteer(this); + } +} + +class ForensicGadgeteerEffect extends CostModificationEffectImpl { + + private static final String effectText = "Activated abilities of artifacts you control cost {1} less to activate." + + " This effect can't reduce the mana in that cost to less than one mana"; + + public ForensicGadgeteerEffect() { + super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = effectText; + } + + private ForensicGadgeteerEffect(final ForensicGadgeteerEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Player controller = game.getPlayer(abilityToModify.getControllerId()); + if (controller != null) { + int reduceMax = CardUtil.calculateActualPossibleGenericManaReduction(abilityToModify.getManaCostsToPay().getMana(), 1, 1); + if (reduceMax <= 0) { + return true; + } + CardUtil.reduceCost(abilityToModify, reduceMax); + return true; + } + return false; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify.getAbilityType() == AbilityType.ACTIVATED + || (abilityToModify.getAbilityType() == AbilityType.MANA + && (abilityToModify instanceof ActivatedAbility))) { + // Activated abilities of artifacts + Permanent permanent = game.getPermanentOrLKIBattlefield(abilityToModify.getSourceId()); + return permanent != null + && permanent.isArtifact(game) + && permanent.isControlledBy(source.getControllerId()); + } + return false; + } + + @Override + public ForensicGadgeteerEffect copy() { + return new ForensicGadgeteerEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForgottenAncient.java b/Mage.Sets/src/mage/cards/f/ForgottenAncient.java index 16cc37ad86a..da6ef78de1a 100644 --- a/Mage.Sets/src/mage/cards/f/ForgottenAncient.java +++ b/Mage.Sets/src/mage/cards/f/ForgottenAncient.java @@ -1,4 +1,3 @@ - package mage.cards.f; import mage.MageInt; @@ -28,11 +27,6 @@ import java.util.UUID; * @author Blinke */ public final class ForgottenAncient extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature"); - - static { - filter.add(AnotherPredicate.instance); - } public ForgottenAncient(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); @@ -58,82 +52,89 @@ public final class ForgottenAncient extends CardImpl { return new ForgottenAncient(this); } - class CounterMovement { +} + +class ForgottenAncientEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + ForgottenAncientEffect() { + super(Outcome.Benefit); + this.staticText = "you may move any number of +1/+1 counters from {this} onto other creatures."; + } + + private ForgottenAncientEffect(final ForgottenAncientEffect effect) { + super(effect); + } + + @Override + public ForgottenAncientEffect copy() { + return new ForgottenAncientEffect(this); + } + + static class CounterMovement { public UUID target; public int counters; } - class ForgottenAncientEffect extends OneShotEffect { + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - public ForgottenAncientEffect() { - super(Outcome.Benefit); - this.staticText = "you may move any number of +1/+1 counters from {this} onto other creatures."; + if (controller == null || sourcePermanent == null) { + return false; } - private ForgottenAncientEffect(final ForgottenAncientEffect effect) { - super(effect); + int numCounters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); + if (numCounters == 0) { + return false; } - @Override - public ForgottenAncientEffect copy() { - return new ForgottenAncientEffect(this); - } + List counterMovements = new ArrayList<>(); - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - - if (controller == null || sourcePermanent == null) { - return false; + do { + Target target = new TargetCreaturePermanent(1, 1, filter, true); + if (!target.canChoose(controller.getId(), source, game)) { + break; } - int numCounters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); - if (numCounters == 0) { - return false; + if (!target.choose(Outcome.BoostCreature, source.getControllerId(), source.getSourceId(), source, game)) { + break; } - List counterMovements = new ArrayList<>(); + int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", game); + if (amountToMove == 0) { + break; + } - do { - Target target = new TargetCreaturePermanent(1, 1, filter, true); - if (!target.canChoose(controller.getId(), source, game)) { - break; - } - - if (!target.choose(Outcome.BoostCreature, source.getControllerId(), source.getSourceId(), source, game)) { - break; - } - - int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", game); - if (amountToMove == 0) { - break; - } - - boolean previouslyChosen = false; - for (CounterMovement cm : counterMovements) { - if (cm.target.equals(target.getFirstTarget())) { - cm.counters += amountToMove; - previouslyChosen = true; - } - } - if (!previouslyChosen) { - CounterMovement cm = new CounterMovement(); - cm.target = target.getFirstTarget(); - cm.counters = amountToMove; - counterMovements.add(cm); - } - - numCounters -= amountToMove; - - } while (numCounters > 0 && controller.chooseUse(Outcome.Benefit, "Move additional counters?", source, game)); - - //Move all the counters for each chosen creature + boolean previouslyChosen = false; for (CounterMovement cm : counterMovements) { - sourcePermanent.removeCounters(CounterType.P1P1.createInstance(cm.counters), source, game); - game.getPermanent(cm.target).addCounters(CounterType.P1P1.createInstance(cm.counters), source.getControllerId(), source, game); + if (cm.target.equals(target.getFirstTarget())) { + cm.counters += amountToMove; + previouslyChosen = true; + } } - return true; + if (!previouslyChosen) { + CounterMovement cm = new CounterMovement(); + cm.target = target.getFirstTarget(); + cm.counters = amountToMove; + counterMovements.add(cm); + } + + numCounters -= amountToMove; + + } while (numCounters > 0 && controller.chooseUse(Outcome.Benefit, "Move additional counters?", source, game)); + + //Move all the counters for each chosen creature + for (CounterMovement cm : counterMovements) { + sourcePermanent.removeCounters(CounterType.P1P1.createInstance(cm.counters), source, game); + game.getPermanent(cm.target).addCounters(CounterType.P1P1.createInstance(cm.counters), source.getControllerId(), source, game); } + return true; } } diff --git a/Mage.Sets/src/mage/cards/f/ForgottenLore.java b/Mage.Sets/src/mage/cards/f/ForgottenLore.java index d514832255b..ef6bffd2ad8 100644 --- a/Mage.Sets/src/mage/cards/f/ForgottenLore.java +++ b/Mage.Sets/src/mage/cards/f/ForgottenLore.java @@ -61,7 +61,7 @@ class ForgottenLoreEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (you != null && opponent != null) { FilterCard filter = new FilterCard(); filter.add(new OwnerIdPredicate(you.getId())); diff --git a/Mage.Sets/src/mage/cards/f/FoulRenewal.java b/Mage.Sets/src/mage/cards/f/FoulRenewal.java index 7d835838b32..0734de2c83b 100644 --- a/Mage.Sets/src/mage/cards/f/FoulRenewal.java +++ b/Mage.Sets/src/mage/cards/f/FoulRenewal.java @@ -65,7 +65,7 @@ class FoulRenewalEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { int xValue = card.getToughness().getValue() * -1; controller.moveCards(card, Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/f/FractalHarness.java b/Mage.Sets/src/mage/cards/f/FractalHarness.java index 25efcc29d69..8910caf727d 100644 --- a/Mage.Sets/src/mage/cards/f/FractalHarness.java +++ b/Mage.Sets/src/mage/cards/f/FractalHarness.java @@ -5,6 +5,7 @@ import mage.abilities.common.AttacksAttachedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,7 +35,7 @@ public final class FractalHarness extends CardImpl { // Whenever equipped creature attacks, double the number of +1/+1 counters on it. this.addAbility(new AttacksAttachedTriggeredAbility( - new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT + new DoubleCountersTargetEffect(CounterType.P1P1), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT )); // Equip {2} @@ -88,30 +89,3 @@ class FractalHarnessTokenEffect extends OneShotEffect { return true; } } - -class FractalHarnessDoubleEffect extends OneShotEffect { - - FractalHarnessDoubleEffect() { - super(Outcome.Benefit); - staticText = "double the number of +1/+1 counters on it"; - } - - private FractalHarnessDoubleEffect(final FractalHarnessDoubleEffect effect) { - super(effect); - } - - @Override - public FractalHarnessDoubleEffect copy() { - return new FractalHarnessDoubleEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); - if (permanent == null) { - return false; - } - return permanent.addCounters(CounterType.P1P1.createInstance(permanent.getCounters(game).getCount(CounterType.P1P1)), - source.getControllerId(), source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/f/FranticInventory.java b/Mage.Sets/src/mage/cards/f/FranticInventory.java index 7ab40c6030b..01a373e8ac6 100644 --- a/Mage.Sets/src/mage/cards/f/FranticInventory.java +++ b/Mage.Sets/src/mage/cards/f/FranticInventory.java @@ -3,6 +3,8 @@ package mage.cards.f; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -13,6 +15,7 @@ import java.util.UUID; /** * @author TheElk801 + * modified tiera3 - added cardHint */ public final class FranticInventory extends CardImpl { @@ -23,9 +26,14 @@ public final class FranticInventory extends CardImpl { } private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + private static final Hint hint = new ValueHint( + "Cards named Frantic Inventory in your graveyard", new CardsInControllerGraveyardCount(filter) + ); public FranticInventory(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + this.getSpellAbility().addHint(hint); // Draw a card, then draw cards equal to the number of cards named Frantic Inventory in your graveyard. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); diff --git a/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java b/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java index a7f0be4b6ae..4bc66e08ebf 100644 --- a/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java +++ b/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java @@ -1,20 +1,19 @@ package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.Target; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetSacrifice; +import java.util.UUID; + /** * * @author TheElk801 @@ -89,12 +88,11 @@ class FrayingOmnipotenceEffect extends OneShotEffect { if (player == null) { continue; } - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - int creaturesToSacrifice = (int) Math.ceil(game.getBattlefield().count(filter, player.getId(), source, game) / 2.0); + int creaturesToSacrifice = (int) Math.ceil(game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURE, player.getId(), source, game) / 2.0); if (creaturesToSacrifice == 0) { continue; } - Target target = new TargetSacrifice(creaturesToSacrifice, filter); + TargetSacrifice target = new TargetSacrifice(creaturesToSacrifice, StaticFilters.FILTER_PERMANENT_CREATURE); target.chooseTarget(Outcome.Sacrifice, playerId, source, game); for (UUID permanentId : target.getTargets()) { Permanent permanent = game.getPermanent(permanentId); diff --git a/Mage.Sets/src/mage/cards/f/FreeForAll.java b/Mage.Sets/src/mage/cards/f/FreeForAll.java index 630f6bc995c..6a858935ef0 100644 --- a/Mage.Sets/src/mage/cards/f/FreeForAll.java +++ b/Mage.Sets/src/mage/cards/f/FreeForAll.java @@ -5,6 +5,7 @@ import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.Cards; @@ -36,7 +37,8 @@ public final class FreeForAll extends CardImpl { this.addAbility(new BeginningOfUpkeepTriggeredAbility(new FreeForAllReturnFromExileEffect(), TargetController.ANY, false)); // When Free-for-All leaves the battlefield, put all cards exiled with it into their owners' graveyards. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new FreeForAllLeavesBattlefieldEffect(), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.GRAVEYARD) + .setText("put all cards exiled with it into their owners' graveyards"), false)); } private FreeForAll(final FreeForAll card) { @@ -111,29 +113,3 @@ class FreeForAllReturnFromExileEffect extends OneShotEffect { return player.moveCards(exiledCards.getRandom(game), Zone.BATTLEFIELD, source, game); } } - -class FreeForAllLeavesBattlefieldEffect extends OneShotEffect { - - FreeForAllLeavesBattlefieldEffect() { - super(Outcome.Detriment); - this.staticText = "put all cards exiled with it into their owners' graveyards"; - } - - private FreeForAllLeavesBattlefieldEffect(final FreeForAllLeavesBattlefieldEffect effect) { - super(effect); - } - - @Override - public FreeForAllLeavesBattlefieldEffect copy() { - return new FreeForAllLeavesBattlefieldEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - ExileZone exZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); - return controller != null - && exZone != null - && controller.moveCards(exZone.getCards(game), Zone.GRAVEYARD, source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/f/FrightfulDelusion.java b/Mage.Sets/src/mage/cards/f/FrightfulDelusion.java index 8fa7ae3f62b..de0b59cafc2 100644 --- a/Mage.Sets/src/mage/cards/f/FrightfulDelusion.java +++ b/Mage.Sets/src/mage/cards/f/FrightfulDelusion.java @@ -58,7 +58,7 @@ class FrightfulDelusionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { StackObject spell = game.getStack().getStackObject( - targetPointer.getFirst(game, source)); + getTargetPointer().getFirst(game, source)); Cost cost = ManaUtil.createManaCost(1, false); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/f/FromTheCatacombs.java b/Mage.Sets/src/mage/cards/f/FromTheCatacombs.java index 442d40b32b4..c591c7977de 100644 --- a/Mage.Sets/src/mage/cards/f/FromTheCatacombs.java +++ b/Mage.Sets/src/mage/cards/f/FromTheCatacombs.java @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -20,15 +21,13 @@ import java.util.UUID; */ public final class FromTheCatacombs extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public FromTheCatacombs(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); // Return target creature card from a graveyard to the battlefield with a corpse counter on it. If that creature would leave the battlefield, exile it instead of putting it anywhere else. this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.CORPSE.createInstance())); this.getSpellAbility().addEffect(new LeaveBattlefieldExileTargetReplacementEffect("that creature")); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // You take the initiative. this.getSpellAbility().addEffect(new TakeTheInitiativeEffect().concatBy("
    ")); diff --git a/Mage.Sets/src/mage/cards/f/FrontlineMedic.java b/Mage.Sets/src/mage/cards/f/FrontlineMedic.java index f3ef1946b11..43b5ff38997 100644 --- a/Mage.Sets/src/mage/cards/f/FrontlineMedic.java +++ b/Mage.Sets/src/mage/cards/f/FrontlineMedic.java @@ -1,4 +1,3 @@ - package mage.cards.f; import mage.MageInt; @@ -18,7 +17,7 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterSpell; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.VariableManaCostPredicate; import mage.target.TargetSpell; @@ -45,8 +44,7 @@ public final class FrontlineMedic extends CardImpl { this.toughness = new MageInt(3); // Battalion - Whenever Frontline Medic and at least two other creatures attack, creatures you control gain indestructible until end of turn. - Effect effect = new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent(), false); - effect.setText("creatures you control gain indestructible until end of turn"); + Effect effect = new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES, false); this.addAbility(new BattalionAbility(effect)); // Sacrifice Frontline Medic: Counter target spell with {X} in its mana cost unless its controller pays {3}. diff --git a/Mage.Sets/src/mage/cards/f/FulfillContract.java b/Mage.Sets/src/mage/cards/f/FulfillContract.java index da690eede5b..b311c455f03 100644 --- a/Mage.Sets/src/mage/cards/f/FulfillContract.java +++ b/Mage.Sets/src/mage/cards/f/FulfillContract.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -10,13 +9,13 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -26,7 +25,7 @@ import mage.target.common.TargetCreaturePermanent; public final class FulfillContract extends CardImpl { private static final FilterCreaturePermanent filterBountyCreature = new FilterCreaturePermanent("creature with a bounty counter on it"); - private static final FilterControlledCreaturePermanent filterRogueOrHunter = new FilterControlledCreaturePermanent("Rogue or Hunter you control"); + private static final FilterControlledPermanent filterRogueOrHunter = new FilterControlledPermanent("Rogue or Hunter you control"); static { filterBountyCreature.add(CounterType.BOUNTY.getPredicate()); @@ -39,7 +38,7 @@ public final class FulfillContract extends CardImpl { // Destroy target creature with a bounty counter on it. If that creature is destroyed this way, you may put a +1/+1 counter on target Rogue or Hunter you control. this.getSpellAbility().addEffect(new FulfillContractEffect()); this.getSpellAbility().addTarget(new TargetCreaturePermanent(filterBountyCreature)); - this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(filterRogueOrHunter)); + this.getSpellAbility().addTarget(new TargetControlledPermanent(filterRogueOrHunter)); } diff --git a/Mage.Sets/src/mage/cards/f/FulgentDistraction.java b/Mage.Sets/src/mage/cards/f/FulgentDistraction.java index 8991951f4f4..a4209b808e8 100644 --- a/Mage.Sets/src/mage/cards/f/FulgentDistraction.java +++ b/Mage.Sets/src/mage/cards/f/FulgentDistraction.java @@ -1,5 +1,3 @@ - - package mage.cards.f; import java.util.ArrayList; @@ -42,20 +40,18 @@ public final class FulgentDistraction extends CardImpl { class FulgentDistractionEffect extends OneShotEffect { - private static String text = "Choose two target creatures. Tap those creatures, then unattach all Equipment from them"; - - FulgentDistractionEffect ( ) { + FulgentDistractionEffect() { super(Outcome.Tap); - staticText = text; + staticText = "Choose two target creatures. Tap those creatures, then unattach all Equipment from them"; } - FulgentDistractionEffect ( FulgentDistractionEffect effect ) { + private FulgentDistractionEffect(FulgentDistractionEffect effect) { super(effect); } @Override public boolean apply(Game game, Ability source) { - for ( UUID target : targetPointer.getTargets(game, source) ) { + for ( UUID target : getTargetPointer().getTargets(game, source) ) { Permanent creature = game.getPermanent(target); List copiedAttachments = new ArrayList<>(creature.getAttachments()); @@ -63,13 +59,14 @@ class FulgentDistractionEffect extends OneShotEffect { Permanent equipment = game.getPermanent(equipmentId); boolean isEquipment = false; - for ( Ability ability : equipment.getAbilities() ) { - if ( ability instanceof EquipAbility ) { + for (Ability ability : equipment.getAbilities()) { + if (ability instanceof EquipAbility) { isEquipment = true; + break; } } - if ( isEquipment ) { + if (isEquipment) { creature.removeAttachment(equipmentId, source, game); } } diff --git a/Mage.Sets/src/mage/cards/g/GOTOJAIL.java b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java index 7fd2a3b1958..7f4fa3d699f 100644 --- a/Mage.Sets/src/mage/cards/g/GOTOJAIL.java +++ b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java @@ -70,7 +70,7 @@ class GoToJailExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); // If GO TO JAIL leaves the battlefield before its triggered ability resolves, // the target creature won't be exiled. diff --git a/Mage.Sets/src/mage/cards/g/GallifreyStands.java b/Mage.Sets/src/mage/cards/g/GallifreyStands.java index 7536158d6b1..69d010ff01c 100644 --- a/Mage.Sets/src/mage/cards/g/GallifreyStands.java +++ b/Mage.Sets/src/mage/cards/g/GallifreyStands.java @@ -16,7 +16,7 @@ import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.*; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.players.Player; @@ -29,7 +29,7 @@ import java.util.UUID; public final class GallifreyStands extends CardImpl { private static final FilterCreatureCard filter = new FilterCreatureCard("a Doctor creature card"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent(SubType.DOCTOR, "Doctors"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.DOCTOR, "Doctors"); static { filter.add(SubType.DOCTOR.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/g/GalvanicBombardment.java b/Mage.Sets/src/mage/cards/g/GalvanicBombardment.java index 319645a2568..408b6a76962 100644 --- a/Mage.Sets/src/mage/cards/g/GalvanicBombardment.java +++ b/Mage.Sets/src/mage/cards/g/GalvanicBombardment.java @@ -6,6 +6,8 @@ import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -18,6 +20,7 @@ import mage.target.common.TargetCreaturePermanent; /** * * @author fireshoes + * modified tiera3 - added Hint */ public final class GalvanicBombardment extends CardImpl { @@ -26,6 +29,9 @@ public final class GalvanicBombardment extends CardImpl { static { filter.add(new NamePredicate("Galvanic Bombardment")); } + private static final Hint hint = new ValueHint( + "Cards named Galvanic Bombardment in your graveyard", new GalvanicBombardmentCardsInControllerGraveyardCount(filter) + ); public GalvanicBombardment(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{R}"); @@ -35,6 +41,7 @@ public final class GalvanicBombardment extends CardImpl { effect.setText("{this} deals X damage to target creature, where X is 2 plus the number of cards named {this} in your graveyard"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(hint); } private GalvanicBombardment(final GalvanicBombardment card) { diff --git a/Mage.Sets/src/mage/cards/g/GandalfWestwardVoyager.java b/Mage.Sets/src/mage/cards/g/GandalfWestwardVoyager.java index 4e6da0c815e..8e76d93738b 100644 --- a/Mage.Sets/src/mage/cards/g/GandalfWestwardVoyager.java +++ b/Mage.Sets/src/mage/cards/g/GandalfWestwardVoyager.java @@ -73,7 +73,7 @@ class GandalfWestwardVoyagerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getSpellOrLKIStack(targetPointer.getFirst(game, source)); + Spell spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source)); List typesSpell = spell == null ? new ArrayList<>() : spell.getCardType(game); boolean foundCard = false; diff --git a/Mage.Sets/src/mage/cards/g/GangrenousGoliath.java b/Mage.Sets/src/mage/cards/g/GangrenousGoliath.java index f938a316f34..e5a885a2f3c 100644 --- a/Mage.Sets/src/mage/cards/g/GangrenousGoliath.java +++ b/Mage.Sets/src/mage/cards/g/GangrenousGoliath.java @@ -1,18 +1,17 @@ - package mage.cards.g; import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapTargetCost; -import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -20,7 +19,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class GangrenousGoliath extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.CLERIC,"untapped Clerics you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CLERIC,"untapped Clerics you control"); public GangrenousGoliath(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}{B}"); @@ -32,8 +31,8 @@ public final class GangrenousGoliath extends CardImpl { // Tap three untapped Clerics you control: Return Gangrenous Goliath from your graveyard to your hand. this.addAbility(new SimpleActivatedAbility( Zone.GRAVEYARD, - new ReturnToHandSourceEffect(), - new TapTargetCost(new TargetControlledCreaturePermanent(3, 3, filter, true)))); + new ReturnSourceFromGraveyardToHandEffect(), + new TapTargetCost(new TargetControlledPermanent(3, 3, filter, true)))); } private GangrenousGoliath(final GangrenousGoliath card) { diff --git a/Mage.Sets/src/mage/cards/g/GarrukPrimalHunter.java b/Mage.Sets/src/mage/cards/g/GarrukPrimalHunter.java index dc3c05c34b2..3f1e67e7d4f 100644 --- a/Mage.Sets/src/mage/cards/g/GarrukPrimalHunter.java +++ b/Mage.Sets/src/mage/cards/g/GarrukPrimalHunter.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -13,7 +12,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.SuperType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; @@ -74,7 +73,7 @@ class GarrukPrimalHunterEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null) { int amount = 0; - for (Permanent p : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), source.getControllerId(), game)) { + for (Permanent p : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game)) { if (p.getPower().getValue() > amount) { amount = p.getPower().getValue(); } diff --git a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java index 3de96b421f1..5a26e6a9fab 100644 --- a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java +++ b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java @@ -98,7 +98,7 @@ class GarrukRelentlessDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { int damage = permanent.getPower().getValue(); permanent.damage(3, source.getSourceId(), source, game, false, true); diff --git a/Mage.Sets/src/mage/cards/g/GarthOneEye.java b/Mage.Sets/src/mage/cards/g/GarthOneEye.java index 944632a03e7..4a2b385deeb 100644 --- a/Mage.Sets/src/mage/cards/g/GarthOneEye.java +++ b/Mage.Sets/src/mage/cards/g/GarthOneEye.java @@ -69,7 +69,7 @@ class GarthOneEyeEffect extends OneShotEffect { .findCards(new CardCriteria().setCodes("LEA")) .stream() .filter(cardInfo -> names.contains(cardInfo.getName())) - .collect(Collectors.toMap(CardInfo::getName, CardInfo::getCard))); + .collect(Collectors.toMap(CardInfo::getName, CardInfo::createCard))); } GarthOneEyeEffect() { diff --git a/Mage.Sets/src/mage/cards/g/GateColossus.java b/Mage.Sets/src/mage/cards/g/GateColossus.java index 1737658205c..c7221bba29e 100644 --- a/Mage.Sets/src/mage/cards/g/GateColossus.java +++ b/Mage.Sets/src/mage/cards/g/GateColossus.java @@ -1,21 +1,19 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.GateYouControlCount; import mage.abilities.effects.common.PutOnLibrarySourceEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.common.GateYouControlHint; import mage.abilities.keyword.DauntAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.util.CardUtil; import java.util.UUID; @@ -24,6 +22,8 @@ import java.util.UUID; */ public final class GateColossus extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GATE, "a Gate"); + public GateColossus(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{8}"); @@ -45,7 +45,7 @@ public final class GateColossus extends CardImpl { Zone.GRAVEYARD, new PutOnLibrarySourceEffect( true, "put {this} from your graveyard on top of your library" - ), GateColossusCostReductionEffect.filter, true + ), filter, true )); } @@ -58,38 +58,3 @@ public final class GateColossus extends CardImpl { return new GateColossus(this); } } - -class GateColossusCostReductionEffect extends CostModificationEffectImpl { - - static final FilterControlledPermanent filter = new FilterControlledPermanent("a Gate"); - - static { - filter.add(SubType.GATE.getPredicate()); - } - - public GateColossusCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each Gate you control"; - } - - protected GateColossusCostReductionEffect(final GateColossusCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int count = game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game).size(); - CardUtil.reduceCost(abilityToModify, count); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify.getSourceId().equals(source.getSourceId()); - } - - @Override - public GateColossusCostReductionEffect copy() { - return new GateColossusCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/g/GatesAblaze.java b/Mage.Sets/src/mage/cards/g/GatesAblaze.java index 50d06102ccc..bbd1045fba1 100644 --- a/Mage.Sets/src/mage/cards/g/GatesAblaze.java +++ b/Mage.Sets/src/mage/cards/g/GatesAblaze.java @@ -31,7 +31,8 @@ public final class GatesAblaze extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); // Gates Ablaze deals X damage to each creature, where X is the number of Gates you control. - this.getSpellAbility().addEffect(new DamageAllEffect(xValue, StaticFilters.FILTER_PERMANENT_CREATURE)); + this.getSpellAbility().addEffect(new DamageAllEffect(xValue, StaticFilters.FILTER_PERMANENT_CREATURE) + .setText("{this} deals X damage to each creature, where X is the number of Gates you control")); } private GatesAblaze(final GatesAblaze card) { diff --git a/Mage.Sets/src/mage/cards/g/Geistwave.java b/Mage.Sets/src/mage/cards/g/Geistwave.java index 869dfed9f0f..d0e51aca361 100644 --- a/Mage.Sets/src/mage/cards/g/Geistwave.java +++ b/Mage.Sets/src/mage/cards/g/Geistwave.java @@ -57,7 +57,7 @@ class GeistwaveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (player == null || permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/g/GemcutterBuccaneer.java b/Mage.Sets/src/mage/cards/g/GemcutterBuccaneer.java index 50a1a5e81d8..ab4d88023d8 100644 --- a/Mage.Sets/src/mage/cards/g/GemcutterBuccaneer.java +++ b/Mage.Sets/src/mage/cards/g/GemcutterBuccaneer.java @@ -36,7 +36,7 @@ import mage.target.TargetPermanent; */ public final class GemcutterBuccaneer extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.PIRATE, "Pirate"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.PIRATE, "Pirate"); public GemcutterBuccaneer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); diff --git a/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java index 8c1ddd3d577..faceffa370e 100644 --- a/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java +++ b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java @@ -67,8 +67,8 @@ class GeneralsRegaliaEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { - this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GerrardsHourglassPendant.java b/Mage.Sets/src/mage/cards/g/GerrardsHourglassPendant.java index d8e0b385681..e3692b6174f 100644 --- a/Mage.Sets/src/mage/cards/g/GerrardsHourglassPendant.java +++ b/Mage.Sets/src/mage/cards/g/GerrardsHourglassPendant.java @@ -1,14 +1,12 @@ package mage.cards.g; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SkipExtraTurnsAbility; import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.keyword.FlashAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -17,12 +15,14 @@ import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.card.PutIntoGraveFromBattlefieldThisTurnPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.watchers.common.CardsPutIntoGraveyardWatcher; import java.util.UUID; +/** + * @author PurpleCrowbar + */ public final class GerrardsHourglassPendant extends CardImpl { public GerrardsHourglassPendant(UUID ownerId, CardSetInfo setInfo) { @@ -33,7 +33,7 @@ public final class GerrardsHourglassPendant extends CardImpl { this.addAbility(FlashAbility.getInstance()); // If a player would begin an extra turn, that player skips that turn instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GerrardsHourglassPendantSkipExtraTurnsEffect())); + this.addAbility(new SkipExtraTurnsAbility()); // {4}, {T}, Exile Gerrard's Hourglass Pendant: Return to the battlefield tapped all artifact, creature, // enchantment, and land cards in your graveyard that were put there from the battlefield this turn. @@ -54,44 +54,6 @@ public final class GerrardsHourglassPendant extends CardImpl { } } -class GerrardsHourglassPendantSkipExtraTurnsEffect extends ReplacementEffectImpl { - - GerrardsHourglassPendantSkipExtraTurnsEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "If a player would begin an extra turn, that player skips that turn instead"; - } - - private GerrardsHourglassPendantSkipExtraTurnsEffect(final GerrardsHourglassPendantSkipExtraTurnsEffect effect) { - super(effect); - } - - @Override - public GerrardsHourglassPendantSkipExtraTurnsEffect copy() { - return new GerrardsHourglassPendantSkipExtraTurnsEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - MageObject sourceObject = game.getObject(source); - if (player != null && sourceObject != null) { - game.informPlayers(sourceObject.getLogName() + ": Extra turn of " + player.getLogName() + " skipped"); - } - return true; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.EXTRA_TURN; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return true; - } - -} - class GerrardsHourglassPendantReanimateEffect extends OneShotEffect { private static final FilterCard filter = new FilterCard(); diff --git a/Mage.Sets/src/mage/cards/g/GhastlyConscription.java b/Mage.Sets/src/mage/cards/g/GhastlyConscription.java index a67e15a8739..4c2984be612 100644 --- a/Mage.Sets/src/mage/cards/g/GhastlyConscription.java +++ b/Mage.Sets/src/mage/cards/g/GhastlyConscription.java @@ -1,18 +1,12 @@ package mage.cards.g; -import java.util.*; -import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType; +import mage.abilities.effects.keyword.ManifestEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.StaticFilters; @@ -20,6 +14,8 @@ import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import java.util.*; + /** * * @author LevelX2 @@ -48,7 +44,10 @@ class GhastlyConscriptionEffect extends OneShotEffect { GhastlyConscriptionEffect() { super(Outcome.PutCreatureInPlay); - this.staticText = "Exile all creature cards from target player's graveyard in a face-down pile, shuffle that pile, then manifest those cards. (To manifest a card, put it onto the battlefield face down as a 2/2 creature. Turn it face up at any time for its mana cost if it's a creature card.)"; + this.staticText = "Exile all creature cards from target player's graveyard in a face-down pile, " + + "shuffle that pile, then manifest those cards. " + + "(To manifest a card, put it onto the battlefield face down as a 2/2 creature. " + + "Turn it face up at any time for its mana cost if it's a creature card.)"; } private GhastlyConscriptionEffect(final GhastlyConscriptionEffect effect) { @@ -64,35 +63,22 @@ class GhastlyConscriptionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (controller != null && targetPlayer != null) { - List cardsToManifest = new ArrayList<>(); - for (Card card : targetPlayer.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game)) { - cardsToManifest.add(card); - controller.moveCardToExileWithInfo(card, null, "", source, game, Zone.GRAVEYARD, true); - } - if (cardsToManifest.isEmpty()) { - return true; - } - Collections.shuffle(cardsToManifest); - game.informPlayers(controller.getLogName() + " shuffles the face-down pile"); - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - for (Card card : cardsToManifest) { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); - } - Set toBattlefield = new LinkedHashSet(); - toBattlefield.addAll(cardsToManifest); - controller.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game, false, true, false, null); + if (controller == null || targetPlayer == null) { + return false; + } + List cardsToManifest = new ArrayList<>(); + for (Card card : targetPlayer.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game)) { + cardsToManifest.add(card); + controller.moveCardToExileWithInfo(card, null, "", source, game, Zone.GRAVEYARD, true); + card.setFaceDown(true, game); + } + if (cardsToManifest.isEmpty()) { return true; } - return false; + Collections.shuffle(cardsToManifest); + game.informPlayers(controller.getLogName() + " shuffles the face-down pile"); + game.getState().processAction(game); + ManifestEffect.doManifestCards(game, source, controller, new LinkedHashSet<>(cardsToManifest)); + return true; } } diff --git a/Mage.Sets/src/mage/cards/g/GhaveGuruOfSpores.java b/Mage.Sets/src/mage/cards/g/GhaveGuruOfSpores.java index 5ee4e8f6ce2..e883e08fcb1 100644 --- a/Mage.Sets/src/mage/cards/g/GhaveGuruOfSpores.java +++ b/Mage.Sets/src/mage/cards/g/GhaveGuruOfSpores.java @@ -20,10 +20,8 @@ import mage.constants.SuperType; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.permanent.token.SaprolingToken; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -46,7 +44,7 @@ public final class GhaveGuruOfSpores extends CardImpl { // {1}, Remove a +1/+1 counter from a creature you control: Create a 1/1 green Saproling creature token. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new SaprolingToken()), new GenericManaCost(1)); - ability.addCost(new RemoveCounterCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true), CounterType.P1P1)); + ability.addCost(new RemoveCounterCost(new TargetControlledCreaturePermanent().withNotTarget(true), CounterType.P1P1)); this.addAbility(ability); // {1}, Sacrifice a creature: Put a +1/+1 counter on target creature. diff --git a/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java b/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java index f49c99f95f0..5229a87fdf2 100644 --- a/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java +++ b/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java @@ -123,7 +123,7 @@ class GhoulsNightOutTypeChangingEffect extends ContinuousEffectImpl { @Override public boolean apply(Layer layer, SubLayer subLayer, Ability source, Game game) { boolean isActive = false; - for (UUID permId : targetPointer.getTargets(game, source)) { + for (UUID permId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permId); if (permanent != null) { switch (layer) { diff --git a/Mage.Sets/src/mage/cards/g/GiantOyster.java b/Mage.Sets/src/mage/cards/g/GiantOyster.java index 830af87196f..3d4a7c74d48 100644 --- a/Mage.Sets/src/mage/cards/g/GiantOyster.java +++ b/Mage.Sets/src/mage/cards/g/GiantOyster.java @@ -1,6 +1,5 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; @@ -28,8 +27,9 @@ import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author noahg */ public final class GiantOyster extends CardImpl { @@ -103,23 +103,24 @@ class GiantOysterCreateDelayedTriggerEffects extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent oyster = game.getPermanent(source.getSourceId()); - Permanent tappedCreature = game.getPermanent(source.getFirstTarget()); - if (oyster != null && tappedCreature != null) { - Effect addCountersEffect = new AddCountersTargetEffect(CounterType.M1M1.createInstance(1)); - addCountersEffect.setTargetPointer(getTargetPointer().getFixedTarget(game, source)); - DelayedTriggeredAbility drawStepAbility = new AtTheBeginOfYourNextDrawStepDelayedTriggeredAbility(addCountersEffect, Duration.Custom, false); - drawStepAbility.setControllerId(source.getControllerId()); - UUID drawStepAbilityUUID = game.addDelayedTriggeredAbility(drawStepAbility, source); - - DelayedTriggeredAbility leaveUntapDelayedTriggeredAbility = new GiantOysterLeaveUntapDelayedTriggeredAbility(drawStepAbilityUUID); - leaveUntapDelayedTriggeredAbility.getEffects().get(0).setTargetPointer(new FixedTarget(tappedCreature, game)); - game.addDelayedTriggeredAbility(leaveUntapDelayedTriggeredAbility, source); - return true; - } + Permanent oyster = game.getPermanent(source.getSourceId()); + Permanent tappedCreature = game.getPermanent(source.getFirstTarget()); + FixedTarget fixedTarget = getTargetPointer().getFirstAsFixedTarget(game, source); + if (controller == null || oyster == null || tappedCreature == null || fixedTarget == null) { + return false; } - return false; + + Effect addCountersEffect = new AddCountersTargetEffect(CounterType.M1M1.createInstance(1)); + addCountersEffect.setTargetPointer(fixedTarget); + DelayedTriggeredAbility drawStepAbility = new AtTheBeginOfYourNextDrawStepDelayedTriggeredAbility(addCountersEffect, Duration.Custom, false); + drawStepAbility.setControllerId(source.getControllerId()); + UUID drawStepAbilityUUID = game.addDelayedTriggeredAbility(drawStepAbility, source); + + DelayedTriggeredAbility leaveUntapDelayedTriggeredAbility = new GiantOysterLeaveUntapDelayedTriggeredAbility(drawStepAbilityUUID); + leaveUntapDelayedTriggeredAbility.getEffects().get(0).setTargetPointer(new FixedTarget(tappedCreature, game)); + game.addDelayedTriggeredAbility(leaveUntapDelayedTriggeredAbility, source); + return true; + } } diff --git a/Mage.Sets/src/mage/cards/g/GiantsGrasp.java b/Mage.Sets/src/mage/cards/g/GiantsGrasp.java index a6600e37704..87f1342896a 100644 --- a/Mage.Sets/src/mage/cards/g/GiantsGrasp.java +++ b/Mage.Sets/src/mage/cards/g/GiantsGrasp.java @@ -9,7 +9,7 @@ import mage.constants.SubType; import mage.abilities.Ability; import mage.abilities.effects.common.AttachEffect; import mage.constants.Outcome; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.TargetPermanent; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; @@ -23,7 +23,7 @@ import mage.target.common.TargetNonlandPermanent; */ public final class GiantsGrasp extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.GIANT); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GIANT); public GiantsGrasp(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}"); diff --git a/Mage.Sets/src/mage/cards/g/GideonsPhalanx.java b/Mage.Sets/src/mage/cards/g/GideonsPhalanx.java index b121d8df007..8210079e24a 100644 --- a/Mage.Sets/src/mage/cards/g/GideonsPhalanx.java +++ b/Mage.Sets/src/mage/cards/g/GideonsPhalanx.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -13,7 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.permanent.token.KnightToken; /** @@ -30,7 +29,7 @@ public final class GideonsPhalanx extends CardImpl { // Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, creatures you control gain indestructible until end of turn. Effect effect = new ConditionalOneShotEffect( - new AddContinuousEffectToGame(new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent())), + new AddContinuousEffectToGame(new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES)), SpellMasteryCondition.instance, "
    Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, creatures you control gain indestructible until end of turn"); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/g/GiftOfFangs.java b/Mage.Sets/src/mage/cards/g/GiftOfFangs.java index a4b00861c14..6d0159ee676 100644 --- a/Mage.Sets/src/mage/cards/g/GiftOfFangs.java +++ b/Mage.Sets/src/mage/cards/g/GiftOfFangs.java @@ -66,7 +66,6 @@ class GiftOfFangsEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { - super.init(source, game); if (affectedObjectsSet) { // Added boosts of activated or triggered abilities exist independent from the source they are created by // so a continuous effect for the permanent itself with the attachment is created @@ -75,13 +74,15 @@ class GiftOfFangsEffect extends ContinuousEffectImpl { this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo()))); } } + + super.init(source, game); // must call at the end due target pointer setup } @Override public boolean apply(Game game, Ability source) { Permanent permanent = null; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage.Sets/src/mage/cards/g/GildedDrake.java b/Mage.Sets/src/mage/cards/g/GildedDrake.java index fe59fbddc80..20c836ec93d 100644 --- a/Mage.Sets/src/mage/cards/g/GildedDrake.java +++ b/Mage.Sets/src/mage/cards/g/GildedDrake.java @@ -81,13 +81,13 @@ class GildedDrakeEffect extends OneShotEffect { return false; } - if (targetPointer.getFirst(game, source) == null || game.getPermanent(targetPointer.getFirst(game, source)) == null) { + if (getTargetPointer().getFirst(game, source) == null || game.getPermanent(getTargetPointer().getFirst(game, source)) == null) { sourceObject.sacrifice(source, game); return true; } ContinuousEffect effect = new ExchangeControlTargetEffect(Duration.EndOfGame, "", true); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/g/GiltLeafArchdruid.java b/Mage.Sets/src/mage/cards/g/GiltLeafArchdruid.java index 4349324ef8c..857208cbb1a 100644 --- a/Mage.Sets/src/mage/cards/g/GiltLeafArchdruid.java +++ b/Mage.Sets/src/mage/cards/g/GiltLeafArchdruid.java @@ -93,7 +93,7 @@ class GiltLeafArchdruidEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && targetPlayer != null) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_LANDS, targetPlayer.getId(), game)) { if (permanent != null) { diff --git a/Mage.Sets/src/mage/cards/g/GiveTake.java b/Mage.Sets/src/mage/cards/g/GiveTake.java index 89a32bc735f..2e0a6a5b8ba 100644 --- a/Mage.Sets/src/mage/cards/g/GiveTake.java +++ b/Mage.Sets/src/mage/cards/g/GiveTake.java @@ -65,7 +65,7 @@ class TakeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature != null) { int numberCounters = creature.getCounters(game).getCount(CounterType.P1P1); if (numberCounters > 0) { diff --git a/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java b/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java index 644f84797e4..9ea5894c518 100644 --- a/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java +++ b/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java @@ -114,7 +114,7 @@ class GixYawgmothPraetorDrawEffect extends DoIfCostPaid { @Override protected Player getPayingPlayer(Game game, Ability source) { - return game.getPlayer(targetPointer.getFirst(game, source)); + return game.getPlayer(getTargetPointer().getFirst(game, source)); } } diff --git a/Mage.Sets/src/mage/cards/g/GlaringSpotlight.java b/Mage.Sets/src/mage/cards/g/GlaringSpotlight.java index 358199a8069..dbebfae7724 100644 --- a/Mage.Sets/src/mage/cards/g/GlaringSpotlight.java +++ b/Mage.Sets/src/mage/cards/g/GlaringSpotlight.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -15,17 +14,16 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; /** * Gatecrash FAQ 21.01.2013 - * + *

    * Creatures your opponents control don't actually lose hexproof, although you * will ignore hexproof for purposes of choosing targets of spells and abilities * you control. - * + *

    * Creatures that come under your control after Glaring Spotlight's last ability * resolves won't have hexproof but can't be blocked that turn. * @@ -41,9 +39,9 @@ public final class GlaringSpotlight extends CardImpl { // {3}, Sacrifice Glaring Spotlight: Creatures you control gain hexproof until end of turn and can't be blocked this turn. Ability ability = new SimpleActivatedAbility( - Zone.BATTLEFIELD, new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE, false), + Zone.BATTLEFIELD, new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES, false), new GenericManaCost(3)); - ability.addEffect(new CantBeBlockedAllEffect(new FilterControlledCreaturePermanent(), Duration.EndOfTurn)); + ability.addEffect(new CantBeBlockedAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, Duration.EndOfTurn).setText("and can't be blocked this turn")); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GleefulDemolition.java b/Mage.Sets/src/mage/cards/g/GleefulDemolition.java index 8c021912f98..f054c330921 100644 --- a/Mage.Sets/src/mage/cards/g/GleefulDemolition.java +++ b/Mage.Sets/src/mage/cards/g/GleefulDemolition.java @@ -58,7 +58,7 @@ class GleefulDemolitionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null || player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/g/GoblinBangchuckers.java b/Mage.Sets/src/mage/cards/g/GoblinBangchuckers.java index dc1b3fbe6d9..930c4c67f34 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinBangchuckers.java +++ b/Mage.Sets/src/mage/cards/g/GoblinBangchuckers.java @@ -63,12 +63,12 @@ class GoblinBangchuckersEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { if (controller.flipCoin(source, game, true)) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(2, source.getSourceId(), source, game, false, true); return true; } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(2, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/g/GoblinBruiser.java b/Mage.Sets/src/mage/cards/g/GoblinBruiser.java deleted file mode 100644 index ddd1352fd2a..00000000000 --- a/Mage.Sets/src/mage/cards/g/GoblinBruiser.java +++ /dev/null @@ -1,33 +0,0 @@ -package mage.cards.g; - -import mage.MageInt; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author JayDi85 - */ -public final class GoblinBruiser extends CardImpl { - - public GoblinBruiser(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); - this.subtype.add(SubType.GOBLIN); - this.subtype.add(SubType.WARRIOR); - - this.power = new MageInt(3); - this.toughness = new MageInt(3); - } - - private GoblinBruiser(final GoblinBruiser card) { - super(card); - } - - @Override - public GoblinBruiser copy() { - return new GoblinBruiser(this); - } -} diff --git a/Mage.Sets/src/mage/cards/g/GoblinCharbelcher.java b/Mage.Sets/src/mage/cards/g/GoblinCharbelcher.java index 8e4f73941af..9a020f87837 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinCharbelcher.java +++ b/Mage.Sets/src/mage/cards/g/GoblinCharbelcher.java @@ -93,11 +93,11 @@ class GoblinCharbelcherEffect extends OneShotEffect { damage *= 2; } - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(damage, source.getSourceId(), source, game, false, true); } else { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(damage, source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/g/GoblinFestival.java b/Mage.Sets/src/mage/cards/g/GoblinFestival.java index 3df9861c8c8..867d480e63f 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinFestival.java +++ b/Mage.Sets/src/mage/cards/g/GoblinFestival.java @@ -114,9 +114,7 @@ class GoblinFestivalGainControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (targetPointer != null) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); - } + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { return permanent.changeControllerId(controller, game, source); } diff --git a/Mage.Sets/src/mage/cards/g/GoblinMaskmaker.java b/Mage.Sets/src/mage/cards/g/GoblinMaskmaker.java new file mode 100644 index 00000000000..876ffd557ab --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoblinMaskmaker.java @@ -0,0 +1,74 @@ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.game.Game; + +/** + * + * @author DominionSpy + */ +public final class GoblinMaskmaker extends CardImpl { + + public GoblinMaskmaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Whenever Goblin Maskmaker attacks, face-down spells you cast this turn cost {1} less to cast. + this.addAbility(new AttacksTriggeredAbility(new GoblinMaskmakerEffect())); + } + + private GoblinMaskmaker(final GoblinMaskmaker card) { + super(card); + } + + @Override + public GoblinMaskmaker copy() { + return new GoblinMaskmaker(this); + } +} + +class GoblinMaskmakerEffect extends SpellsCostReductionControllerEffect { + + private static final FilterCard filter = new FilterCard("face-down spells"); + + GoblinMaskmakerEffect() { + super(filter, 1); + this.duration = Duration.EndOfTurn; + this.staticText = "face-down spells you cast this turn cost {1} less to cast"; + } + + private GoblinMaskmakerEffect(final GoblinMaskmakerEffect effect) { + super(effect); + } + + @Override + public GoblinMaskmakerEffect copy() { + return new GoblinMaskmakerEffect(this); + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify instanceof SpellAbility) { + SpellAbility spellAbility = (SpellAbility) abilityToModify; + if (spellAbility.getSpellAbilityCastMode().isFaceDown()) { + return super.applies(abilityToModify, source, game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoblinPsychopath.java b/Mage.Sets/src/mage/cards/g/GoblinPsychopath.java index 295f0b23ba3..fbade67a840 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinPsychopath.java +++ b/Mage.Sets/src/mage/cards/g/GoblinPsychopath.java @@ -59,8 +59,15 @@ class GoblinPsychopathEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { - this.wonFlip = game.getPlayer(source.getControllerId()).flipCoin(source, game, true); super.init(source, game); + + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + discard(); + return; + } + + this.wonFlip = controller.flipCoin(source, game, true); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GoblinSledder.java b/Mage.Sets/src/mage/cards/g/GoblinSledder.java index 556927858e5..1baad15521a 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinSledder.java +++ b/Mage.Sets/src/mage/cards/g/GoblinSledder.java @@ -13,8 +13,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -23,11 +22,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class GoblinSledder extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.GOBLIN, "a Goblin"); public GoblinSledder(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{R}"); diff --git a/Mage.Sets/src/mage/cards/g/GoblinTurncoat.java b/Mage.Sets/src/mage/cards/g/GoblinTurncoat.java index f995d021be6..9f4cd529d1d 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinTurncoat.java +++ b/Mage.Sets/src/mage/cards/g/GoblinTurncoat.java @@ -12,8 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.FilterPermanent; /** * @@ -21,11 +20,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class GoblinTurncoat extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.GOBLIN, "a Goblin"); public GoblinTurncoat(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); diff --git a/Mage.Sets/src/mage/cards/g/GoblinWarrens.java b/Mage.Sets/src/mage/cards/g/GoblinWarrens.java index 5362c0b54da..7152ad16eb5 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinWarrens.java +++ b/Mage.Sets/src/mage/cards/g/GoblinWarrens.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -12,9 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.game.permanent.token.GoblinToken; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -22,12 +20,11 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class GoblinWarrens extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.GOBLIN, "Goblins"); + private static final FilterPermanent filter = new FilterPermanent(SubType.GOBLIN, "Goblins"); public GoblinWarrens(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{R}"); - // {2}{R}, Sacrifice two Goblins: Create three 1/1 red Goblin creature tokens. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new GoblinToken(), 3), new ManaCostsImpl<>("{2}{R}")); ability.addCost(new SacrificeTargetCost(2, filter)); diff --git a/Mage.Sets/src/mage/cards/g/GoblinsOfTheFlarg.java b/Mage.Sets/src/mage/cards/g/GoblinsOfTheFlarg.java index dcb195f5f60..804fa23ea70 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinsOfTheFlarg.java +++ b/Mage.Sets/src/mage/cards/g/GoblinsOfTheFlarg.java @@ -1,4 +1,3 @@ - package mage.cards.g; import mage.MageInt; @@ -9,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -18,11 +17,7 @@ import java.util.UUID; */ public final class GoblinsOfTheFlarg extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Dwarf"); - - static { - filter.add(SubType.DWARF.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DWARF, "a Dwarf"); public GoblinsOfTheFlarg(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); diff --git a/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java b/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java index 4860b17fb3f..4a60817eeff 100644 --- a/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java +++ b/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java @@ -7,18 +7,18 @@ import mage.abilities.common.GodEternalDiesTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import java.util.UUID; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.filter.common.FilterControlledCreaturePermanent; /** * @author TheElk801 @@ -55,8 +55,6 @@ public final class GodEternalRhonas extends CardImpl { } class GodEternalRhonasEffect extends OneShotEffect { - - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); GodEternalRhonasEffect() { super(Outcome.Benefit); @@ -76,7 +74,7 @@ class GodEternalRhonasEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent godEternalRhonas = game.getPermanent(source.getSourceId()); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURES, source.getControllerId(), game)) { if (permanent == null || godEternalRhonas != null && permanent == godEternalRhonas) { diff --git a/Mage.Sets/src/mage/cards/g/GoddricCloakedReveler.java b/Mage.Sets/src/mage/cards/g/GoddricCloakedReveler.java index fed13379ba3..acf329b9259 100644 --- a/Mage.Sets/src/mage/cards/g/GoddricCloakedReveler.java +++ b/Mage.Sets/src/mage/cards/g/GoddricCloakedReveler.java @@ -46,7 +46,7 @@ public final class GoddricCloakedReveler extends CardImpl { // Celebration -- As long as two or more nonland permanents entered the battlefield under your control this turn, Goddric, Cloaked Reveler is a Dragon with base power and toughness 4/4, flying, and "{R}: Dragons you control get +1/+0 until end of turn." Ability dragonFirebreath = new SimpleActivatedAbility( new BoostAllEffect(1, 0, Duration.EndOfTurn, filter, false), - new ManaCostsImpl("{R}") + new ManaCostsImpl<>("{R}") ); Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( diff --git a/Mage.Sets/src/mage/cards/g/GodoBanditWarlord.java b/Mage.Sets/src/mage/cards/g/GodoBanditWarlord.java index 214cb25bb6a..32a87282cf2 100644 --- a/Mage.Sets/src/mage/cards/g/GodoBanditWarlord.java +++ b/Mage.Sets/src/mage/cards/g/GodoBanditWarlord.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -16,7 +15,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; @@ -27,7 +26,7 @@ import mage.target.common.TargetCardInLibrary; public final class GodoBanditWarlord extends CardImpl { private static final FilterCard filter = new FilterCard("an Equipment card"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent(SubType.SAMURAI); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.SAMURAI); static { filter.add(SubType.EQUIPMENT.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/g/GolemArtisan.java b/Mage.Sets/src/mage/cards/g/GolemArtisan.java index ed1c14d6fcd..caf2a80f937 100644 --- a/Mage.Sets/src/mage/cards/g/GolemArtisan.java +++ b/Mage.Sets/src/mage/cards/g/GolemArtisan.java @@ -77,7 +77,7 @@ class GolemArtisanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); Player playerControls = game.getPlayer(source.getControllerId()); if (permanent != null && playerControls != null) { Choice abilityChoice = new ChoiceImpl(); diff --git a/Mage.Sets/src/mage/cards/g/GolgariCharm.java b/Mage.Sets/src/mage/cards/g/GolgariCharm.java index 5fe0b4f363a..4cd696f2ec4 100644 --- a/Mage.Sets/src/mage/cards/g/GolgariCharm.java +++ b/Mage.Sets/src/mage/cards/g/GolgariCharm.java @@ -9,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetEnchantmentPermanent; /** @@ -21,7 +21,6 @@ public final class GolgariCharm extends CardImpl { public GolgariCharm(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}{G}"); - // Choose one — All creatures get -1/-1 until end of turn; this.getSpellAbility().addEffect(new BoostAllEffect(-1, -1, Duration.EndOfTurn)); @@ -31,7 +30,7 @@ public final class GolgariCharm extends CardImpl { this.getSpellAbility().addMode(mode); // or regenerate each creature you control. - mode = new Mode(new RegenerateAllEffect(new FilterControlledCreaturePermanent())); + mode = new Mode(new RegenerateAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java b/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java index 74f4755c185..2e1fc887b02 100644 --- a/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java +++ b/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java @@ -15,7 +15,7 @@ import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Controllable; import mage.game.Game; -import mage.game.permanent.token.SalamnderWarriorToken; +import mage.game.permanent.token.SalamanderWarriorToken; import mage.game.permanent.token.Token; import mage.util.CardUtil; @@ -99,7 +99,7 @@ class GorMuldrakAmphinologistEffect extends OneShotEffect { .forEach(uuid -> creatureMap.compute(uuid, CardUtil::setOrIncrementValue)); int minValue = creatureMap.values().stream().mapToInt(x -> x).min().orElse(0); minValue = Math.max(minValue, 0); - Token token = new SalamnderWarriorToken(); + Token token = new SalamanderWarriorToken(); for (Map.Entry entry : creatureMap.entrySet()) { if (entry.getValue() > minValue) { continue; diff --git a/Mage.Sets/src/mage/cards/g/GoreVassal.java b/Mage.Sets/src/mage/cards/g/GoreVassal.java index 8b6dce02238..83d42417e7d 100644 --- a/Mage.Sets/src/mage/cards/g/GoreVassal.java +++ b/Mage.Sets/src/mage/cards/g/GoreVassal.java @@ -69,12 +69,12 @@ class GoreVassalEffect extends RegenerateTargetEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); + Permanent creature = game.getPermanent(source.getFirstTarget()); if (creature == null || creature.getToughness().getValue() < 1) { this.discard(); - return; } - super.init(source, game); } } diff --git a/Mage.Sets/src/mage/cards/g/GoryosVengeance.java b/Mage.Sets/src/mage/cards/g/GoryosVengeance.java index 1e6fd5d6551..33a9d0421e3 100644 --- a/Mage.Sets/src/mage/cards/g/GoryosVengeance.java +++ b/Mage.Sets/src/mage/cards/g/GoryosVengeance.java @@ -79,7 +79,7 @@ class GoryosVengeanceEffect extends OneShotEffect { if (controller == null) { return false; } - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/g/GougedZealot.java b/Mage.Sets/src/mage/cards/g/GougedZealot.java index a11e7ef4d0d..a8010194a03 100644 --- a/Mage.Sets/src/mage/cards/g/GougedZealot.java +++ b/Mage.Sets/src/mage/cards/g/GougedZealot.java @@ -1,20 +1,19 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.condition.common.DeliriumCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllControlledTargetEffect; import mage.abilities.hint.common.CardTypesInGraveyardHint; -import mage.constants.*; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -35,7 +34,7 @@ public final class GougedZealot extends CardImpl { // Delirium — Whenever Gouged Zealot attacks, if there are four or more card types among cards in your graveyard, Gouged Zealot deals 1 damage to each creature defending player controls. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new AttacksTriggeredAbility(new GougedZealotEffect(), false, null, SetTargetPointer.PLAYER), + new AttacksTriggeredAbility(new DamageAllControlledTargetEffect(1), false, null, SetTargetPointer.PLAYER), DeliriumCondition.instance, "Delirium — Whenever {this} attacks, if there are four or more card types among cards in your graveyard, {this} deals 1 damage to each creature defending player controls." ).addHint(CardTypesInGraveyardHint.YOU)); @@ -50,32 +49,3 @@ public final class GougedZealot extends CardImpl { return new GougedZealot(this); } } - -class GougedZealotEffect extends OneShotEffect { - - GougedZealotEffect() { - super(Outcome.Damage); - this.staticText = "{this} deals 1 damage to each creature defending player controls"; - } - - private GougedZealotEffect(final GougedZealotEffect effect) { - super(effect); - } - - @Override - public GougedZealotEffect copy() { - return new GougedZealotEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - UUID defendingPlayerId = getTargetPointer().getFirst(game, source); - if (defendingPlayerId != null) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, defendingPlayerId, game)) { - permanent.damage(1, source.getSourceId(), source, game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/g/GrafHarvest.java b/Mage.Sets/src/mage/cards/g/GrafHarvest.java index 68e4f5fd0d0..2b75e728052 100644 --- a/Mage.Sets/src/mage/cards/g/GrafHarvest.java +++ b/Mage.Sets/src/mage/cards/g/GrafHarvest.java @@ -17,7 +17,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureCard; import mage.game.permanent.token.ZombieToken; import mage.target.common.TargetCardInYourGraveyard; @@ -28,7 +28,7 @@ import mage.target.common.TargetCardInYourGraveyard; */ public final class GrafHarvest extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Zombies you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Zombies you control"); static { filter.add(SubType.ZOMBIE.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/g/GrafMole.java b/Mage.Sets/src/mage/cards/g/GrafMole.java index a18271cc000..70fa165b2f9 100644 --- a/Mage.Sets/src/mage/cards/g/GrafMole.java +++ b/Mage.Sets/src/mage/cards/g/GrafMole.java @@ -7,7 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,8 +17,6 @@ import java.util.UUID; */ public final class GrafMole extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "a Clue"); - public GrafMole(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); this.subtype.add(SubType.MOLE); @@ -27,7 +25,7 @@ public final class GrafMole extends CardImpl { this.toughness = new MageInt(4); // Whenever you sacrifice a Clue, you gain 3 life. - this.addAbility(new SacrificePermanentTriggeredAbility(new GainLifeEffect(3), filter)); + this.addAbility(new SacrificePermanentTriggeredAbility(new GainLifeEffect(3), StaticFilters.FILTER_CONTROLLED_CLUE)); } private GrafMole(final GrafMole card) { diff --git a/Mage.Sets/src/mage/cards/g/GrandMoffTarkin.java b/Mage.Sets/src/mage/cards/g/GrandMoffTarkin.java index 0564bdced47..0699b72694d 100644 --- a/Mage.Sets/src/mage/cards/g/GrandMoffTarkin.java +++ b/Mage.Sets/src/mage/cards/g/GrandMoffTarkin.java @@ -102,7 +102,7 @@ class GrandMoffTarkinEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index 9181f2d5791..af4674f4632 100644 --- a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java +++ b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java @@ -114,7 +114,7 @@ class GraveBetrayalEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { ContinuousEffect effect = new GraveBetrayalReplacementEffect(); effect.setTargetPointer(new FixedTarget(card.getId())); diff --git a/Mage.Sets/src/mage/cards/g/GravePeril.java b/Mage.Sets/src/mage/cards/g/GravePeril.java index 2f6c1dba1b6..040385e0219 100644 --- a/Mage.Sets/src/mage/cards/g/GravePeril.java +++ b/Mage.Sets/src/mage/cards/g/GravePeril.java @@ -61,7 +61,7 @@ class GravePerilEffect extends OneShotEffect { if (permanent != null) { if (permanent.sacrifice(source, game)) { Effect effect = new DestroyTargetEffect(); - effect.setTargetPointer(this.getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/g/Gravegouger.java b/Mage.Sets/src/mage/cards/g/Gravegouger.java index e16cf4f74bd..78d25387bb8 100644 --- a/Mage.Sets/src/mage/cards/g/Gravegouger.java +++ b/Mage.Sets/src/mage/cards/g/Gravegouger.java @@ -34,7 +34,8 @@ public final class Gravegouger extends CardImpl { this.addAbility(ability); // When Gravegouger leaves the battlefield, return the exiled cards to their owner's graveyard. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.GRAVEYARD).withText(true, false), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.GRAVEYARD) + .withText(true, false, false), false)); } private Gravegouger(final Gravegouger card) { diff --git a/Mage.Sets/src/mage/cards/g/GravespawnSovereign.java b/Mage.Sets/src/mage/cards/g/GravespawnSovereign.java index f750a528bd3..067be5624e6 100644 --- a/Mage.Sets/src/mage/cards/g/GravespawnSovereign.java +++ b/Mage.Sets/src/mage/cards/g/GravespawnSovereign.java @@ -12,6 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.permanent.TappedPredicate; @@ -39,7 +40,7 @@ public final class GravespawnSovereign extends CardImpl { // Tap five untapped Zombies you control: Put target creature card from a graveyard onto the battlefield under your control. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new TapTargetCost(new TargetControlledCreaturePermanent(5, 5, filter, true))); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GravityWell.java b/Mage.Sets/src/mage/cards/g/GravityWell.java index 89294f130b8..cb581457ce4 100644 --- a/Mage.Sets/src/mage/cards/g/GravityWell.java +++ b/Mage.Sets/src/mage/cards/g/GravityWell.java @@ -91,7 +91,7 @@ class GravityWellEffect extends ContinuousEffectImpl { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { switch (layer) { case AbilityAddingRemovingEffects_6: diff --git a/Mage.Sets/src/mage/cards/g/GreaterKraytDragon.java b/Mage.Sets/src/mage/cards/g/GreaterKraytDragon.java index ab5507bc355..f01f52b6f91 100644 --- a/Mage.Sets/src/mage/cards/g/GreaterKraytDragon.java +++ b/Mage.Sets/src/mage/cards/g/GreaterKraytDragon.java @@ -13,7 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -34,7 +34,7 @@ public final class GreaterKraytDragon extends CardImpl { this.addAbility(new MonstrosityAbility("{X}{X}{R}{G}{W}", Integer.MAX_VALUE)); // When Greater Krayt Dragon becomes monstrous, draw a card for each +1/+1 counter on creatures you control. - this.addAbility(new BecomesMonstrousSourceTriggeredAbility(new DrawCardSourceControllerEffect(new CountersCount(CounterType.P1P1, new FilterControlledCreaturePermanent())))); + this.addAbility(new BecomesMonstrousSourceTriggeredAbility(new DrawCardSourceControllerEffect(new CountersCount(CounterType.P1P1, StaticFilters.FILTER_CONTROLLED_CREATURES)))); } diff --git a/Mage.Sets/src/mage/cards/g/GriefTyrant.java b/Mage.Sets/src/mage/cards/g/GriefTyrant.java index 9a0fcea3f88..b0558374f4e 100644 --- a/Mage.Sets/src/mage/cards/g/GriefTyrant.java +++ b/Mage.Sets/src/mage/cards/g/GriefTyrant.java @@ -68,7 +68,7 @@ class GriefTyrantEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); Permanent griefTyrant = game.getPermanentOrLKIBattlefield(source.getSourceId()); int countersOnGriefTyrant = griefTyrant.getCounters(game).getCount(CounterType.M1M1); if (targetCreature != null) { diff --git a/Mage.Sets/src/mage/cards/g/GrimaSarumansFootman.java b/Mage.Sets/src/mage/cards/g/GrimaSarumansFootman.java index 41902bf0c38..3bcc1ddfcdc 100644 --- a/Mage.Sets/src/mage/cards/g/GrimaSarumansFootman.java +++ b/Mage.Sets/src/mage/cards/g/GrimaSarumansFootman.java @@ -67,7 +67,7 @@ class GrimaSarumansFootmanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (player == null || controller == null) { return false; diff --git a/Mage.Sets/src/mage/cards/g/GrolnokTheOmnivore.java b/Mage.Sets/src/mage/cards/g/GrolnokTheOmnivore.java index 1e790d416c4..f09516530cf 100644 --- a/Mage.Sets/src/mage/cards/g/GrolnokTheOmnivore.java +++ b/Mage.Sets/src/mage/cards/g/GrolnokTheOmnivore.java @@ -111,7 +111,7 @@ class GrolnokTheOmnivoreExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && card != null) { return CardUtil.moveCardWithCounter(game, source, controller, card, Zone.EXILED, CounterType.CROAK.createInstance()); } diff --git a/Mage.Sets/src/mage/cards/g/GrowthCycle.java b/Mage.Sets/src/mage/cards/g/GrowthCycle.java index 2f4352915a7..d10efe655f3 100644 --- a/Mage.Sets/src/mage/cards/g/GrowthCycle.java +++ b/Mage.Sets/src/mage/cards/g/GrowthCycle.java @@ -3,6 +3,8 @@ package mage.cards.g; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -15,6 +17,7 @@ import java.util.UUID; /** * @author awjackson + * @modified tiera3 - added cardHint */ public final class GrowthCycle extends CardImpl { @@ -24,6 +27,9 @@ public final class GrowthCycle extends CardImpl { } private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter, 2); private static final String rule = "it gets an additional +2/+2 until end of turn for each " + xValue.getMessage(); + private static final Hint hint = new ValueHint( + "Cards named Growth Cycle in your graveyard", new CardsInControllerGraveyardCount(filter) + ); public GrowthCycle(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); @@ -33,6 +39,7 @@ public final class GrowthCycle extends CardImpl { // It gets an additional +2/+2 until end of turn for each card named Growth Cycle in your graveyard. this.getSpellAbility().addEffect(new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn).setText(rule)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(hint); } private GrowthCycle(final GrowthCycle card) { diff --git a/Mage.Sets/src/mage/cards/g/GruulRagebeast.java b/Mage.Sets/src/mage/cards/g/GruulRagebeast.java index 801776fe013..1f51c5882f4 100644 --- a/Mage.Sets/src/mage/cards/g/GruulRagebeast.java +++ b/Mage.Sets/src/mage/cards/g/GruulRagebeast.java @@ -93,8 +93,9 @@ class GruulRagebeastTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { // that triggers depends on stack order, so make each trigger unique with extra info String triggeredInfo = ""; - if (this.getEffects().get(0).getTargetPointer() != null) { - triggeredInfo = " Your fighting creature: " + this.getEffects().get(0).getTargetPointer().getData("triggeredName") + "."; + String triggeredMana = this.getEffects().get(0).getTargetPointer().getData("triggeredName"); + if (!triggeredMana.isEmpty()) { + triggeredInfo = " Your fighting creature: " + triggeredMana + "."; } return "Whenever {this} or another creature enters the battlefield under your control, " + "that creature fights target creature an opponent controls." + triggeredInfo; @@ -113,7 +114,7 @@ class GruulRagebeastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent triggeredCreature = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent triggeredCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); Permanent target = game.getPermanent(source.getFirstTarget()); if (triggeredCreature != null && target != null diff --git a/Mage.Sets/src/mage/cards/g/GuardDogs.java b/Mage.Sets/src/mage/cards/g/GuardDogs.java index aeb52ddc335..c8cc0430beb 100644 --- a/Mage.Sets/src/mage/cards/g/GuardDogs.java +++ b/Mage.Sets/src/mage/cards/g/GuardDogs.java @@ -67,10 +67,11 @@ class GuardDogsEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); + this.controlledTarget = new TargetControlledPermanent(); this.controlledTarget.withNotTarget(true); this.controlledTarget.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); - super.init(source, game); } diff --git a/Mage.Sets/src/mage/cards/g/GuardianAngel.java b/Mage.Sets/src/mage/cards/g/GuardianAngel.java index 5ef18b5d13b..df10965a220 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianAngel.java +++ b/Mage.Sets/src/mage/cards/g/GuardianAngel.java @@ -19,7 +19,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; @@ -80,10 +79,10 @@ class GuardianAngelEffect extends OneShotEffect { targetName = "player " + targetPlayer.getName(); } ContinuousEffect effect = new PreventDamageToTargetEffect(Duration.EndOfTurn, source.getManaCostsToPay().getX(), false); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); SpecialAction specialAction = new GuardianAngelAction(); - specialAction.getEffects().get(0).setTargetPointer(getTargetPointer()); + specialAction.getEffects().get(0).setTargetPointer(this.getTargetPointer().copy()); specialAction.getEffects().get(0).setText("Prevent the next 1 damage that would be dealt to any target this turn (" + targetName + ")."); new CreateSpecialActionEffect(specialAction).apply(game, source); // Create a hidden delayed triggered ability to remove the special action at end of turn. diff --git a/Mage.Sets/src/mage/cards/g/GuardianProject.java b/Mage.Sets/src/mage/cards/g/GuardianProject.java index a4e002f8188..79d13ad44fa 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianProject.java +++ b/Mage.Sets/src/mage/cards/g/GuardianProject.java @@ -11,14 +11,12 @@ import mage.constants.Outcome; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.OwnerIdPredicate; import mage.filter.predicate.mageobject.CardIdPredicate; import mage.filter.predicate.mageobject.NamePredicate; -import mage.filter.predicate.card.OwnerIdPredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; @@ -82,7 +80,7 @@ class GuardianProjectTriggeredAbility extends EntersBattlefieldAllTriggeredAbili @Override public String getRule() { return "Whenever a nontoken creature enters the battlefield under your control, " + - "if that creature does not have the same name as another creature you control " + + "if it doesn't have the same name as another creature you control " + "or a creature card in your graveyard, draw a card."; } @@ -104,10 +102,7 @@ class GuardianProjectTriggeredAbility extends EntersBattlefieldAllTriggeredAbili filterPermanent.add(new NamePredicate(permanent.getName())); filterPermanent.add(Predicates.not(new CardIdPredicate(permanent.getId()))); filterPermanent.add(new ControllerIdPredicate(controllerId)); - if (!game.getBattlefield().getActivePermanents(filterPermanent, controllerId, game).isEmpty()) { - return false; - } - return true; + return game.getBattlefield().getActivePermanents(filterPermanent, controllerId, game).isEmpty(); } } diff --git a/Mage.Sets/src/mage/cards/g/GuildFeud.java b/Mage.Sets/src/mage/cards/g/GuildFeud.java index e59845e5b0f..70fa84cd03c 100644 --- a/Mage.Sets/src/mage/cards/g/GuildFeud.java +++ b/Mage.Sets/src/mage/cards/g/GuildFeud.java @@ -60,7 +60,7 @@ class GuildFeudEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); Permanent opponentCreature = null; Permanent controllerCreature = null; diff --git a/Mage.Sets/src/mage/cards/h/HagraDiabolist.java b/Mage.Sets/src/mage/cards/h/HagraDiabolist.java index b5e5e9c9191..2e266139cd0 100644 --- a/Mage.Sets/src/mage/cards/h/HagraDiabolist.java +++ b/Mage.Sets/src/mage/cards/h/HagraDiabolist.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.TargetPlayer; import java.util.UUID; @@ -20,12 +19,7 @@ import java.util.UUID; */ public final class HagraDiabolist extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Allies you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "Allies you control"); public HagraDiabolist(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); diff --git a/Mage.Sets/src/mage/cards/h/HalanaKessigRanger.java b/Mage.Sets/src/mage/cards/h/HalanaKessigRanger.java index 0621a1d8573..8f3231bcc1b 100644 --- a/Mage.Sets/src/mage/cards/h/HalanaKessigRanger.java +++ b/Mage.Sets/src/mage/cards/h/HalanaKessigRanger.java @@ -79,7 +79,7 @@ class HalanaKessigRangerTriggerEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( new HalanaKessigRangerDamageEffect( - new MageObjectReference(targetPointer.getFirst(game, source), game) + new MageObjectReference(getTargetPointer().getFirst(game, source), game) ), false, "that creature deals damage equal to its power to target creature" ); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/h/HallowedPriest.java b/Mage.Sets/src/mage/cards/h/HallowedPriest.java new file mode 100644 index 00000000000..f8bc3ea952d --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HallowedPriest.java @@ -0,0 +1,42 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.GainLifeControllerTriggeredAbility; +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 java.util.UUID; + +/** + * @author tiera3 - modified from AjanisPridemate + */ +public final class HallowedPriest extends CardImpl { + + public HallowedPriest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever you gain life, put a +1/+1 counter on Hallowed Priest. + this.addAbility(new GainLifeControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false + )); + } + + private HallowedPriest(final HallowedPriest card) { + super(card); + } + + @Override + public HallowedPriest copy() { + return new HallowedPriest(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/h/HamletVanguard.java b/Mage.Sets/src/mage/cards/h/HamletVanguard.java index 28fc81507f9..140a00b6799 100644 --- a/Mage.Sets/src/mage/cards/h/HamletVanguard.java +++ b/Mage.Sets/src/mage/cards/h/HamletVanguard.java @@ -15,7 +15,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TokenPredicate; @@ -26,7 +26,7 @@ import java.util.UUID; */ public final class HamletVanguard extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.HUMAN); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.HUMAN); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/h/HancockGhoulishMayor.java b/Mage.Sets/src/mage/cards/h/HancockGhoulishMayor.java new file mode 100644 index 00000000000..556eb608e64 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HancockGhoulishMayor.java @@ -0,0 +1,63 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.UndyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HancockGhoulishMayor extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(null); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.or( + SubType.ZOMBIE.getPredicate(), + SubType.MUTANT.getPredicate() + )); + } + + public HancockGhoulishMayor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Each other creature you control that's a Zombie or Mutant gets +X/+X, where X is the number of counters on Hancock, Ghoulish Mayor. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + xValue, xValue, Duration.WhileOnBattlefield, filter, true + ).setText("each other creature you control that's a Zombie or Mutant " + + "gets +X/+X, where X is the number of counters on {this}"))); + + // Undying + this.addAbility(new UndyingAbility()); + } + + private HancockGhoulishMayor(final HancockGhoulishMayor card) { + super(card); + } + + @Override + public HancockGhoulishMayor copy() { + return new HancockGhoulishMayor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HarabazDruid.java b/Mage.Sets/src/mage/cards/h/HarabazDruid.java index a85b7fc7165..0aa64ca393f 100644 --- a/Mage.Sets/src/mage/cards/h/HarabazDruid.java +++ b/Mage.Sets/src/mage/cards/h/HarabazDruid.java @@ -1,4 +1,3 @@ - package mage.cards.h; import java.util.UUID; @@ -11,18 +10,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @author Loki */ public final class HarabazDruid extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Allies you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "Allies you control"); public HarabazDruid(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); diff --git a/Mage.Sets/src/mage/cards/h/HarmlessOffering.java b/Mage.Sets/src/mage/cards/h/HarmlessOffering.java index aab9af32bf1..528c41807f1 100644 --- a/Mage.Sets/src/mage/cards/h/HarmlessOffering.java +++ b/Mage.Sets/src/mage/cards/h/HarmlessOffering.java @@ -1,22 +1,14 @@ - package mage.cards.h; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.TargetPlayerGainControlTargetPermanentEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author fireshoes @@ -27,7 +19,7 @@ public final class HarmlessOffering extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{R}"); // Target opponent gains control of target permanent you control. - this.getSpellAbility().addEffect(new HarmlessOfferingEffect()); + this.getSpellAbility().addEffect(new TargetPlayerGainControlTargetPermanentEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); this.getSpellAbility().addTarget(new TargetControlledPermanent()); } @@ -41,34 +33,3 @@ public final class HarmlessOffering extends CardImpl { return new HarmlessOffering(this); } } - -class HarmlessOfferingEffect extends ContinuousEffectImpl { - - HarmlessOfferingEffect() { - super(Duration.EndOfGame, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.Benefit); - this.staticText = "Target opponent gains control of target permanent you control"; - } - - private HarmlessOfferingEffect(final HarmlessOfferingEffect effect) { - super(effect); - } - - @Override - public HarmlessOfferingEffect copy() { - return new HarmlessOfferingEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - UUID controllerId = source.getTargets().get(0).getFirstTarget(); - Player controller = game.getPlayer(controllerId); - Permanent permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (controller != null && permanent != null) { - permanent.changeControllerId(controllerId, game, source); - } else { - this.discard(); - } - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/h/HarmsWay.java b/Mage.Sets/src/mage/cards/h/HarmsWay.java index b26b1b6c7ac..b5cb7f2ab7a 100644 --- a/Mage.Sets/src/mage/cards/h/HarmsWay.java +++ b/Mage.Sets/src/mage/cards/h/HarmsWay.java @@ -63,8 +63,8 @@ class HarmsWayPreventDamageTargetEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { - this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/h/Haystack.java b/Mage.Sets/src/mage/cards/h/Haystack.java new file mode 100644 index 00000000000..b1f035a6b45 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Haystack.java @@ -0,0 +1,38 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.PhaseOutTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Haystack extends CardImpl { + + public Haystack(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + // {2}, {T}: Target creature you control phases out. + Ability ability = new SimpleActivatedAbility(new PhaseOutTargetEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private Haystack(final Haystack card) { + super(card); + } + + @Override + public Haystack copy() { + return new Haystack(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java b/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java index 29ac0100ea0..8a7f2053daf 100644 --- a/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java +++ b/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java @@ -76,6 +76,7 @@ class HazduhrTheAbbotRedirectDamageEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); amountToRedirect = source.getManaCostsToPay().getX(); } diff --git a/Mage.Sets/src/mage/cards/h/Heal.java b/Mage.Sets/src/mage/cards/h/Heal.java index 271fce269d7..73bc541077d 100644 --- a/Mage.Sets/src/mage/cards/h/Heal.java +++ b/Mage.Sets/src/mage/cards/h/Heal.java @@ -24,7 +24,8 @@ public final class Heal extends CardImpl { // Prevent the next 1 damage that would be dealt to any target this turn. // Draw a card at the beginning of the next turn's upkeep. this.getSpellAbility().addEffect(new PreventDamageToTargetEffect(Duration.EndOfTurn, 1)); - this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility( + new DrawCardSourceControllerEffect(1)), false).concatBy("
    ")); this.getSpellAbility().addTarget(new TargetAnyTarget()); } diff --git a/Mage.Sets/src/mage/cards/h/HealingGrace.java b/Mage.Sets/src/mage/cards/h/HealingGrace.java index d9520df8041..7a98ba94c80 100644 --- a/Mage.Sets/src/mage/cards/h/HealingGrace.java +++ b/Mage.Sets/src/mage/cards/h/HealingGrace.java @@ -62,6 +62,7 @@ class HealingGraceEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); // be sure to note the target source's zcc, etc, if able. if (targetSource.getFirstTarget() != null) { diff --git a/Mage.Sets/src/mage/cards/h/HeartflameDuelist.java b/Mage.Sets/src/mage/cards/h/HeartflameDuelist.java index 0569158ee33..655d68af1fc 100644 --- a/Mage.Sets/src/mage/cards/h/HeartflameDuelist.java +++ b/Mage.Sets/src/mage/cards/h/HeartflameDuelist.java @@ -11,7 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetAnyTarget; @@ -22,7 +22,7 @@ import java.util.UUID; */ public final class HeartflameDuelist extends AdventureCard { - private static final FilterCard filter = new FilterCard("instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("instant and sorcery spells you control"); static { filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate())); diff --git a/Mage.Sets/src/mage/cards/h/Heartmender.java b/Mage.Sets/src/mage/cards/h/Heartmender.java index d86cdc507c4..994126dafc4 100644 --- a/Mage.Sets/src/mage/cards/h/Heartmender.java +++ b/Mage.Sets/src/mage/cards/h/Heartmender.java @@ -1,4 +1,3 @@ - package mage.cards.h; import java.util.UUID; @@ -15,7 +14,7 @@ import mage.constants.Outcome; import mage.constants.TargetController; import mage.counters.Counter; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -53,7 +52,7 @@ class HeartmenderEffect extends OneShotEffect { private final Counter counter; - public HeartmenderEffect(Counter counter) { + HeartmenderEffect(Counter counter) { super(Outcome.BoostCreature); this.counter = counter; staticText = "remove a -1/-1 counter from each creature you control"; @@ -67,11 +66,10 @@ class HeartmenderEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean applied = false; - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - if (game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game).isEmpty()) { + if (game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game).isEmpty()) { return true; } - for (Permanent creature : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game)) { if (creature != null && creature.getCounters(game).getCount(counter.getName()) >= counter.getCount()) { creature.removeCounters(counter.getName(), counter.getCount(), source, game); diff --git a/Mage.Sets/src/mage/cards/h/HedgeWhisperer.java b/Mage.Sets/src/mage/cards/h/HedgeWhisperer.java new file mode 100644 index 00000000000..853668bc27a --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HedgeWhisperer.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SkipUntapOptionalAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class HedgeWhisperer extends CardImpl { + + public HedgeWhisperer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DRUID); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // You may choose not to untap Hedge Whisperer during your untap step. + this.addAbility(new SkipUntapOptionalAbility()); + + // {3}{G}, {T}, Collect evidence 4: Target land you control becomes a 5/5 green Plant Boar creature with haste + // for as long as Hedge Whisperer remains tapped. It's still a land. Activate only as a sorcery. + BecomesCreatureTargetEffect effect = new BecomesCreatureTargetEffect( + new CreatureToken(5, 5, "5/5 green Plant Boar creature with haste") + .withColor("G") + .withSubType(SubType.PLANT) + .withSubType(SubType.BOAR) + .withAbility(HasteAbility.getInstance()), + false, true, Duration.Custom); + + Ability ability = new ActivateAsSorceryActivatedAbility( + new ConditionalContinuousEffect( + effect, + SourceTappedCondition.TAPPED, + "target land you control becomes a 5/5 green Plant Boar creature with haste for as long as {this} remains tapped. It's still a land"), + new ManaCostsImpl<>("{3}{G}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new CollectEvidenceCost(4)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND)); + this.addAbility(ability); + } + + private HedgeWhisperer(final HedgeWhisperer card) { + super(card); + } + + @Override + public HedgeWhisperer copy() { + return new HedgeWhisperer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HellToPay.java b/Mage.Sets/src/mage/cards/h/HellToPay.java new file mode 100644 index 00000000000..5a034e66356 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HellToPay.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HellToPay extends CardImpl { + + public HellToPay(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}"); + + // Hell to Pay deals X damage to target creature. Create a number of tapped Treasure tokens equal to the amount of excess damage dealt to that creature this way. + this.getSpellAbility().addEffect(new HellToPayEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private HellToPay(final HellToPay card) { + super(card); + } + + @Override + public HellToPay copy() { + return new HellToPay(this); + } +} + +class HellToPayEffect extends OneShotEffect { + + HellToPayEffect() { + super(Outcome.Benefit); + staticText = "{this} deals X damage to target creature. Create a number of tapped " + + "Treasure tokens equal to the amount of excess damage dealt to that creature this way"; + } + + private HellToPayEffect(final HellToPayEffect effect) { + super(effect); + } + + @Override + public HellToPayEffect copy() { + return new HellToPayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int damage = source.getManaCostsToPay().getX(); + int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); + permanent.damage(lethal, source.getSourceId(), source, game); + if (damage > lethal) { + new TreasureToken().putOntoBattlefield( + damage - lethal, game, source, source.getControllerId(), true, false + ); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HellholeRats.java b/Mage.Sets/src/mage/cards/h/HellholeRats.java index 4caaf4394a0..e6cc39d4c6a 100644 --- a/Mage.Sets/src/mage/cards/h/HellholeRats.java +++ b/Mage.Sets/src/mage/cards/h/HellholeRats.java @@ -68,7 +68,7 @@ class HellholeRatsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int damage = 0; - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { Cards cards = targetPlayer.discard(1, false, false, source, game); if (!cards.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/h/HellkiteTyrant.java b/Mage.Sets/src/mage/cards/h/HellkiteTyrant.java index c5efc54f896..62afc71eb41 100644 --- a/Mage.Sets/src/mage/cards/h/HellkiteTyrant.java +++ b/Mage.Sets/src/mage/cards/h/HellkiteTyrant.java @@ -132,7 +132,7 @@ class HellkiteTyrantControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && controllerId != null) { return permanent.changeControllerId(controllerId, game, source); } diff --git a/Mage.Sets/src/mage/cards/h/HereticsPunishment.java b/Mage.Sets/src/mage/cards/h/HereticsPunishment.java index 27356b6985f..333d37bedb2 100644 --- a/Mage.Sets/src/mage/cards/h/HereticsPunishment.java +++ b/Mage.Sets/src/mage/cards/h/HereticsPunishment.java @@ -66,12 +66,12 @@ class HereticsPunishmentEffect extends OneShotEffect { .mapToInt(MageObject::getManaValue) .max() .orElse(0); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(maxCost, source.getSourceId(), source, game, false, true); return true; } - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(maxCost, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/h/HickoryWoodlot.java b/Mage.Sets/src/mage/cards/h/HickoryWoodlot.java index ecc70d82c05..56bb98e3b48 100644 --- a/Mage.Sets/src/mage/cards/h/HickoryWoodlot.java +++ b/Mage.Sets/src/mage/cards/h/HickoryWoodlot.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,8 +30,11 @@ public final class HickoryWoodlot extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); // Hickory Woodlot enters the battlefield tapped with two depletion counters on it. - this.addAbility(new EntersBattlefieldTappedAbility()); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2)))); + Ability etbAbility = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with two depletion counters on it" + ); + etbAbility.addEffect(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2))); + this.addAbility(etbAbility); // {tap}, Remove a depletion counter from Hickory Woodlot: Add {G}{G}. If there are no depletion counters on Hickory Woodlot, sacrifice it. Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(2), new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.DEPLETION.createInstance(1))); diff --git a/Mage.Sets/src/mage/cards/h/HidetsugusSecondRite.java b/Mage.Sets/src/mage/cards/h/HidetsugusSecondRite.java index ecd617d688e..c39c70f3930 100644 --- a/Mage.Sets/src/mage/cards/h/HidetsugusSecondRite.java +++ b/Mage.Sets/src/mage/cards/h/HidetsugusSecondRite.java @@ -54,7 +54,7 @@ class HidetsugusSecondRiteEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { if (targetPlayer.getLife() == 10) { targetPlayer.damage(10, source.getSourceId(), source, game); diff --git a/Mage.Sets/src/mage/cards/h/HighPriestOfPenance.java b/Mage.Sets/src/mage/cards/h/HighPriestOfPenance.java index 90c8595edf2..0a33f19b1e1 100644 --- a/Mage.Sets/src/mage/cards/h/HighPriestOfPenance.java +++ b/Mage.Sets/src/mage/cards/h/HighPriestOfPenance.java @@ -1,19 +1,17 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.target.common.TargetNonlandPermanent; +import java.util.UUID; + /** * * @author Plopman @@ -29,7 +27,9 @@ public final class HighPriestOfPenance extends CardImpl { this.toughness = new MageInt(1); //Whenever High Priest of Penance is dealt damage, you may destroy target nonland permanent. - this.addAbility(new HighPriestOfPenanceTriggeredAbility()); + Ability ability = new DealtDamageToSourceTriggeredAbility(new DestroyTargetEffect(), true); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); } private HighPriestOfPenance(final HighPriestOfPenance card) { @@ -41,31 +41,3 @@ public final class HighPriestOfPenance extends CardImpl { return new HighPriestOfPenance(this); } } - -class HighPriestOfPenanceTriggeredAbility extends TriggeredAbilityImpl { - - public HighPriestOfPenanceTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); - this.addTarget(new TargetNonlandPermanent()); - setTriggerPhrase("Whenever {this} is dealt damage, "); - } - - private HighPriestOfPenanceTriggeredAbility(final HighPriestOfPenanceTriggeredAbility ability) { - super(ability); - } - - @Override - public HighPriestOfPenanceTriggeredAbility copy() { - return new HighPriestOfPenanceTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getTargetId().equals(this.sourceId); - } -} diff --git a/Mage.Sets/src/mage/cards/h/HiredTorturer.java b/Mage.Sets/src/mage/cards/h/HiredTorturer.java index 213c1b9fe18..5771667f67a 100644 --- a/Mage.Sets/src/mage/cards/h/HiredTorturer.java +++ b/Mage.Sets/src/mage/cards/h/HiredTorturer.java @@ -73,7 +73,7 @@ class HiredTorturerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null && !player.getHand().isEmpty()) { Cards revealed = new CardsImpl(); revealed.add(player.getHand().getRandom(game)); diff --git a/Mage.Sets/src/mage/cards/h/HisokaMinamoSensei.java b/Mage.Sets/src/mage/cards/h/HisokaMinamoSensei.java index e10d9ca39d9..fb47fcbed7c 100644 --- a/Mage.Sets/src/mage/cards/h/HisokaMinamoSensei.java +++ b/Mage.Sets/src/mage/cards/h/HisokaMinamoSensei.java @@ -62,7 +62,7 @@ class HisokaMinamoSenseiCounterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } @@ -70,7 +70,7 @@ class HisokaMinamoSenseiCounterEffect extends OneShotEffect { .map(DiscardTargetCost::getCards) .flatMap(Collection::stream) .anyMatch(card -> card.getManaValue() == spell.getManaValue())) { - return game.getStack().counter(targetPointer.getFirst(game, source), source, game); + return game.getStack().counter(getTargetPointer().getFirst(game, source), source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/h/HisokasGuard.java b/Mage.Sets/src/mage/cards/h/HisokasGuard.java index bfb076c050e..7f3689bec67 100644 --- a/Mage.Sets/src/mage/cards/h/HisokasGuard.java +++ b/Mage.Sets/src/mage/cards/h/HisokasGuard.java @@ -79,6 +79,7 @@ class HisokasGuardGainAbilityTargetEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); // remember the guarded creature Permanent guardedCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); Permanent hisokasGuard = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/h/HoardSmelterDragon.java b/Mage.Sets/src/mage/cards/h/HoardSmelterDragon.java index 194323c2d57..eb98f3e095a 100644 --- a/Mage.Sets/src/mage/cards/h/HoardSmelterDragon.java +++ b/Mage.Sets/src/mage/cards/h/HoardSmelterDragon.java @@ -78,6 +78,7 @@ class HoardSmelterEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); Card targeted = game.getCard(source.getFirstTarget()); if (targeted != null) { costValue = targeted.getManaValue(); diff --git a/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java b/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java index 1c5e4a24c0b..32b17b4bf0c 100644 --- a/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java +++ b/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java @@ -12,7 +12,7 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.card.CastFromZonePredicate; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -28,7 +28,7 @@ import java.util.UUID; */ public final class HoardingBroodlord extends CardImpl { - private static final FilterCard filter = new FilterCard("spells you cast from exile"); + private static final FilterNonlandCard filter = new FilterNonlandCard("spells you cast from exile"); static { filter.add(new CastFromZonePredicate(Zone.EXILED)); diff --git a/Mage.Sets/src/mage/cards/h/HofriGhostforge.java b/Mage.Sets/src/mage/cards/h/HofriGhostforge.java index 1ef5d0e009b..1a5a7d8b30a 100644 --- a/Mage.Sets/src/mage/cards/h/HofriGhostforge.java +++ b/Mage.Sets/src/mage/cards/h/HofriGhostforge.java @@ -102,7 +102,7 @@ class HofriGhostforgeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (player == null || card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/h/HoldForRansom.java b/Mage.Sets/src/mage/cards/h/HoldForRansom.java index 72de32e956c..705da1e0c95 100644 --- a/Mage.Sets/src/mage/cards/h/HoldForRansom.java +++ b/Mage.Sets/src/mage/cards/h/HoldForRansom.java @@ -95,7 +95,7 @@ class HoldForRansomSacrificeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent aura = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent aura = game.getPermanent(getTargetPointer().getFirst(game, source)); if (aura == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/h/HolgaRelentlessRager.java b/Mage.Sets/src/mage/cards/h/HolgaRelentlessRager.java index 1fcd88f006f..f1f2c53089f 100644 --- a/Mage.Sets/src/mage/cards/h/HolgaRelentlessRager.java +++ b/Mage.Sets/src/mage/cards/h/HolgaRelentlessRager.java @@ -55,15 +55,16 @@ public final class HolgaRelentlessRager extends CardImpl { } } -class HolgaRelentlessRagerEffect extends OneShotEffect { - private enum HolgaRelentlessRagerPredicate implements Predicate { - instance; +enum HolgaRelentlessRagerPredicate implements Predicate { + instance; - @Override - public boolean apply(Permanent input, Game game) { - return game.getPlayer(game.getCombat().getDefenderId(input.getId())) != null; - } + @Override + public boolean apply(Permanent input, Game game) { + return game.getPlayer(game.getCombat().getDefenderId(input.getId())) != null; } +} + +class HolgaRelentlessRagerEffect extends OneShotEffect { private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); diff --git a/Mage.Sets/src/mage/cards/h/HollowSpecter.java b/Mage.Sets/src/mage/cards/h/HollowSpecter.java index 42e9f978109..e688c889f8a 100644 --- a/Mage.Sets/src/mage/cards/h/HollowSpecter.java +++ b/Mage.Sets/src/mage/cards/h/HollowSpecter.java @@ -70,7 +70,7 @@ class HollowSpecterEffect extends OneShotEffect { if (targetPlayer != null && controller != null && controller.chooseUse(Outcome.Benefit, "Pay {X}?", source, game)) { int payCount = ManaUtil.playerPaysXGenericMana(true, "Hollow Specter", controller, source, game); if (payCount > 0) { - return new DiscardCardYouChooseTargetEffect(payCount).setTargetPointer(targetPointer).apply(game, source); + return new DiscardCardYouChooseTargetEffect(payCount).setTargetPointer(this.getTargetPointer().copy()).apply(game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/h/HopeAgainstHope.java b/Mage.Sets/src/mage/cards/h/HopeAgainstHope.java index fcb418eb72a..9bb3891b204 100644 --- a/Mage.Sets/src/mage/cards/h/HopeAgainstHope.java +++ b/Mage.Sets/src/mage/cards/h/HopeAgainstHope.java @@ -14,7 +14,7 @@ import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -35,7 +35,7 @@ public final class HopeAgainstHope extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets +1/+1 for each creature you control. - PermanentsOnBattlefieldCount amount = new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent(), 1); + PermanentsOnBattlefieldCount amount = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE, 1); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(amount, amount, Duration.WhileOnBattlefield))); // As long as enchanted creature is a Human, it has first strike. diff --git a/Mage.Sets/src/mage/cards/h/HopelessNightmare.java b/Mage.Sets/src/mage/cards/h/HopelessNightmare.java index 22cd32369c7..da709b03af5 100644 --- a/Mage.Sets/src/mage/cards/h/HopelessNightmare.java +++ b/Mage.Sets/src/mage/cards/h/HopelessNightmare.java @@ -34,7 +34,7 @@ public final class HopelessNightmare extends CardImpl { this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(new ScryEffect(2, false))); // {2}{B}: Sacrifice Hopeless Nightmare. - this.addAbility(new SimpleActivatedAbility(new SacrificeSourceEffect(), new ManaCostsImpl("{2}{B}"))); + this.addAbility(new SimpleActivatedAbility(new SacrificeSourceEffect(), new ManaCostsImpl<>("{2}{B}"))); } private HopelessNightmare(final HopelessNightmare card) { diff --git a/Mage.Sets/src/mage/cards/h/HornOfPlenty.java b/Mage.Sets/src/mage/cards/h/HornOfPlenty.java index d2a0098ce43..38b4f89c1ff 100644 --- a/Mage.Sets/src/mage/cards/h/HornOfPlenty.java +++ b/Mage.Sets/src/mage/cards/h/HornOfPlenty.java @@ -62,7 +62,7 @@ class HornOfPlentyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player caster = game.getPlayer(targetPointer.getFirst(game, source)); + Player caster = game.getPlayer(getTargetPointer().getFirst(game, source)); if (caster != null) { if (caster.chooseUse(Outcome.DrawCard, "Pay {1} to draw a card at the beginning of the next end step?", source, game)) { Cost cost = new ManaCostsImpl<>("{1}"); diff --git a/Mage.Sets/src/mage/cards/h/Hubris.java b/Mage.Sets/src/mage/cards/h/Hubris.java index b47ab577722..9251b25a0cb 100644 --- a/Mage.Sets/src/mage/cards/h/Hubris.java +++ b/Mage.Sets/src/mage/cards/h/Hubris.java @@ -69,7 +69,7 @@ class HubrisReturnEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent creature = game.getPermanent(targetId); if (creature != null) { Cards cardsToHand = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/h/HumbleDefector.java b/Mage.Sets/src/mage/cards/h/HumbleDefector.java index e4e96571006..c647ca1a29a 100644 --- a/Mage.Sets/src/mage/cards/h/HumbleDefector.java +++ b/Mage.Sets/src/mage/cards/h/HumbleDefector.java @@ -5,22 +5,20 @@ import mage.abilities.Ability; import mage.abilities.common.ActivateIfConditionActivatedAbility; import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.hint.common.MyTurnHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; import mage.target.common.TargetOpponent; import java.util.UUID; /** - * @author jeffwadsworth + * @author xenohedron */ public final class HumbleDefector extends CardImpl { @@ -32,7 +30,9 @@ public final class HumbleDefector extends CardImpl { this.toughness = new MageInt(1); // {T}: Draw two cards. Target opponent gains control of Humble Defector. Activate this ability only during your turn. - Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, new HumbleDefectorEffect(), new TapSourceCost(), MyTurnCondition.instance); + Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, + new DrawCardSourceControllerEffect(2), new TapSourceCost(), MyTurnCondition.instance); + ability.addEffect(new TargetPlayerGainControlSourceEffect()); ability.addTarget(new TargetOpponent()); ability.addHint(MyTurnHint.instance); this.addAbility(ability); @@ -48,66 +48,3 @@ public final class HumbleDefector extends CardImpl { return new HumbleDefector(this); } } - -class HumbleDefectorEffect extends OneShotEffect { - - HumbleDefectorEffect() { - super(Outcome.Discard); - this.staticText = "Draw two cards. Target opponent gains control of {this}."; - } - - private HumbleDefectorEffect(final HumbleDefectorEffect effect) { - super(effect); - } - - @Override - public HumbleDefectorEffect copy() { - return new HumbleDefectorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - controller.drawCards(2, source, game); - } - Permanent humbleDefector = source.getSourcePermanentIfItStillExists(game); - Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (targetOpponent != null && humbleDefector != null) { - ContinuousEffect effect = new HumbleDefectorControlSourceEffect(); - game.addEffect(effect, source); - game.informPlayers(humbleDefector.getName() + " is now controlled by " + targetOpponent.getLogName()); - return true; - } - return false; - } -} - -class HumbleDefectorControlSourceEffect extends ContinuousEffectImpl { - - HumbleDefectorControlSourceEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - } - - private HumbleDefectorControlSourceEffect(final HumbleDefectorControlSourceEffect effect) { - super(effect); - } - - @Override - public HumbleDefectorControlSourceEffect copy() { - return new HumbleDefectorControlSourceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player targetOpponent = game.getPlayer(source.getFirstTarget()); - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null && targetOpponent != null) { - permanent.changeControllerId(targetOpponent.getId(), game, source); - } else { - // no valid target exists, effect can be discarded - discard(); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HungryLynx.java b/Mage.Sets/src/mage/cards/h/HungryLynx.java index 2fee319e171..e654581a62d 100644 --- a/Mage.Sets/src/mage/cards/h/HungryLynx.java +++ b/Mage.Sets/src/mage/cards/h/HungryLynx.java @@ -1,9 +1,8 @@ - package mage.cards.h; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability;import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; @@ -13,39 +12,30 @@ import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.abilities.keyword.ProtectionAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.DeathtouchRatToken; -import mage.target.Target; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author Saga */ public final class HungryLynx extends CardImpl { - private static final FilterControlledCreaturePermanent filterCat = new FilterControlledCreaturePermanent("Cats"); - static { - filterCat.add(SubType.CAT.getPredicate()); - } + private static final FilterControlledPermanent filterCat = new FilterControlledPermanent(SubType.CAT, "Cats"); private static final FilterCard filterProRat = new FilterCard("Rats"); static { filterProRat.add(SubType.RAT.getPredicate()); } - private static final FilterCreaturePermanent filterRat = new FilterCreaturePermanent("a Rat"); - static { - filterRat.add(SubType.RAT.getPredicate()); - } + private static final FilterPermanent filterRat = new FilterPermanent(SubType.RAT, "a Rat"); public HungryLynx(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); @@ -60,8 +50,7 @@ public final class HungryLynx extends CardImpl { // At the beginning of your end step, target opponent creates a 1/1 black Rat creature token with deathtouch. Ability ability = new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new CreateTokenTargetEffect(new DeathtouchRatToken()), TargetController.YOU, null, false); - Target target = new TargetOpponent(); - ability.addTarget(target); + ability.addTarget(new TargetOpponent()); this.addAbility(ability); // Whenever a Rat dies, put a +1/+1 counter on each Cat you control. diff --git a/Mage.Sets/src/mage/cards/h/HuntingVelociraptor.java b/Mage.Sets/src/mage/cards/h/HuntingVelociraptor.java index e2fd71deb62..9cef9d28b06 100644 --- a/Mage.Sets/src/mage/cards/h/HuntingVelociraptor.java +++ b/Mage.Sets/src/mage/cards/h/HuntingVelociraptor.java @@ -11,8 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.FilterCard; -import mage.filter.predicate.Predicates; +import mage.filter.common.FilterNonlandCard; /** * @@ -20,10 +19,9 @@ import mage.filter.predicate.Predicates; */ public final class HuntingVelociraptor extends CardImpl { - private static final FilterCard filter = new FilterCard("Dinosaur spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("Dinosaur spells you cast"); static { - filter.add(Predicates.not(CardType.LAND.getPredicate())); filter.add(SubType.DINOSAUR.getPredicate()); } diff --git a/Mage.Sets/src/mage/cards/h/HuntingWilds.java b/Mage.Sets/src/mage/cards/h/HuntingWilds.java index 150e9505747..57a85580afd 100644 --- a/Mage.Sets/src/mage/cards/h/HuntingWilds.java +++ b/Mage.Sets/src/mage/cards/h/HuntingWilds.java @@ -82,13 +82,13 @@ class HuntingWildsEffect extends OneShotEffect { if (sourceEffect instanceof SearchLibraryPutInPlayEffect) { Cards foundCards = new CardsImpl(((SearchLibraryPutInPlayEffect) sourceEffect).getTargets()); if (!foundCards.isEmpty()) { - FixedTargets fixedTargets = new FixedTargets(foundCards, game); + FixedTargets blueprintTarget = new FixedTargets(foundCards, game); UntapTargetEffect untapEffect = new UntapTargetEffect(); - untapEffect.setTargetPointer(fixedTargets); + untapEffect.setTargetPointer(blueprintTarget.copy()); untapEffect.apply(game, source); BecomesCreatureTargetEffect becomesCreatureEffect = new BecomesCreatureTargetEffect(new HuntingWildsToken(), false, true, Duration.Custom); - becomesCreatureEffect.setTargetPointer(fixedTargets); + becomesCreatureEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(becomesCreatureEffect, source); } return true; diff --git a/Mage.Sets/src/mage/cards/h/HurkylsRecall.java b/Mage.Sets/src/mage/cards/h/HurkylsRecall.java index 4faff28b02a..08d5554078b 100644 --- a/Mage.Sets/src/mage/cards/h/HurkylsRecall.java +++ b/Mage.Sets/src/mage/cards/h/HurkylsRecall.java @@ -52,9 +52,9 @@ class HurkylsRecallReturnToHandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (targetPointer.getFirst(game, source) != null) { + if (getTargetPointer().getFirst(game, source) != null) { FilterPermanent filter = new FilterArtifactPermanent(); - filter.add(new OwnerIdPredicate(targetPointer.getFirst(game, source))); + filter.add(new OwnerIdPredicate(getTargetPointer().getFirst(game, source))); return new ReturnToHandFromBattlefieldAllEffect(filter).apply(game, source); } return false; diff --git a/Mage.Sets/src/mage/cards/h/HurlIntoHistory.java b/Mage.Sets/src/mage/cards/h/HurlIntoHistory.java index 31fc165c81c..f317747763f 100644 --- a/Mage.Sets/src/mage/cards/h/HurlIntoHistory.java +++ b/Mage.Sets/src/mage/cards/h/HurlIntoHistory.java @@ -69,7 +69,7 @@ class HurlIntoHistoryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackObject == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/h/HymnOfRebirth.java b/Mage.Sets/src/mage/cards/h/HymnOfRebirth.java index 3b3d9effda3..5951e008836 100644 --- a/Mage.Sets/src/mage/cards/h/HymnOfRebirth.java +++ b/Mage.Sets/src/mage/cards/h/HymnOfRebirth.java @@ -6,6 +6,7 @@ import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffec import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -19,7 +20,7 @@ public final class HymnOfRebirth extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{G}{W}"); // Put target creature card from a graveyard onto the battlefield under your control. - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); } diff --git a/Mage.Sets/src/mage/cards/i/IceCauldron.java b/Mage.Sets/src/mage/cards/i/IceCauldron.java index b2f0a7ac604..ceff7dcc9dd 100644 --- a/Mage.Sets/src/mage/cards/i/IceCauldron.java +++ b/Mage.Sets/src/mage/cards/i/IceCauldron.java @@ -137,7 +137,7 @@ class IceCauldronCastFromExileEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (targetPointer.getTargets(game, source).contains(objectId) + if (getTargetPointer().getTargets(game, source).contains(objectId) && game.getState().getZone(objectId) == Zone.EXILED) { Player player = game.getPlayer(source.getControllerId()); Card card = game.getCard(objectId); diff --git a/Mage.Sets/src/mage/cards/i/IceCave.java b/Mage.Sets/src/mage/cards/i/IceCave.java index 307803c7c43..e5f6028fae1 100644 --- a/Mage.Sets/src/mage/cards/i/IceCave.java +++ b/Mage.Sets/src/mage/cards/i/IceCave.java @@ -62,7 +62,7 @@ class IceCaveEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - Spell spell = (Spell) game.getStack().getStackObject(targetPointer.getFirst(game, source)); + Spell spell = (Spell) game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (sourcePermanent != null && spell != null && controller != null) { Player spellController = game.getPlayer(spell.getControllerId()); Cost cost = new ManaCostsImpl<>(spell.getSpellAbility() == null ? "" : spell.getSpellAbility().getManaCosts().getText()); diff --git a/Mage.Sets/src/mage/cards/i/IdentityThief.java b/Mage.Sets/src/mage/cards/i/IdentityThief.java index 68f6d09fd17..1a9ecce99c8 100644 --- a/Mage.Sets/src/mage/cards/i/IdentityThief.java +++ b/Mage.Sets/src/mage/cards/i/IdentityThief.java @@ -110,6 +110,7 @@ class IdentityThiefEffect extends OneShotEffect { ContinuousEffect copyEffect = new CopyEffect(Duration.EndOfTurn, targetPermanent, source.getSourceId()); copyEffect.setTargetPointer(new FixedTarget(sourcePermanent.getId(), game)); game.addEffect(copyEffect, source); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); if (controller.moveCardsToExile(targetPermanent, source, game, true, exileZoneId, sourcePermanent.getName())) { Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, true); diff --git a/Mage.Sets/src/mage/cards/i/IgniteMemories.java b/Mage.Sets/src/mage/cards/i/IgniteMemories.java index 67f4b784edb..5c97fb75013 100644 --- a/Mage.Sets/src/mage/cards/i/IgniteMemories.java +++ b/Mage.Sets/src/mage/cards/i/IgniteMemories.java @@ -51,7 +51,7 @@ class IgniteMemoriesEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(targetPointer.getFirst(game, source)); + Player controller = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { if (!controller.getHand().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/i/IizukaTheRuthless.java b/Mage.Sets/src/mage/cards/i/IizukaTheRuthless.java index 327fadf0a59..2b6da907957 100644 --- a/Mage.Sets/src/mage/cards/i/IizukaTheRuthless.java +++ b/Mage.Sets/src/mage/cards/i/IizukaTheRuthless.java @@ -1,4 +1,3 @@ - package mage.cards.i; import java.util.UUID; @@ -13,8 +12,8 @@ import mage.abilities.keyword.DoubleStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -22,13 +21,8 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class IizukaTheRuthless extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Samurai"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("Samurai creatures"); - - static { - filter.add(SubType.SAMURAI.getPredicate()); - filter2.add(SubType.SAMURAI.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.SAMURAI, "Samurai"); + private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent(SubType.SAMURAI, "Samurai creatures"); public IizukaTheRuthless(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}{R}"); diff --git a/Mage.Sets/src/mage/cards/i/IllTimedExplosion.java b/Mage.Sets/src/mage/cards/i/IllTimedExplosion.java index 8e4d65b60c3..e2abc2d0ae1 100644 --- a/Mage.Sets/src/mage/cards/i/IllTimedExplosion.java +++ b/Mage.Sets/src/mage/cards/i/IllTimedExplosion.java @@ -47,7 +47,7 @@ class IllTimedExplosionEffect extends OneShotEffect { IllTimedExplosionEffect() { super(Outcome.Benefit); staticText = "Then you may discard two cards. When you do, {this} deals X damage to each creature, " + - "where X is the highest mana value among the discarded cards"; + "where X is the greatest mana value among cards discarded this way"; } private IllTimedExplosionEffect(final IllTimedExplosionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/i/IllicitAuction.java b/Mage.Sets/src/mage/cards/i/IllicitAuction.java index c3983d0d7b2..454476ca0c8 100644 --- a/Mage.Sets/src/mage/cards/i/IllicitAuction.java +++ b/Mage.Sets/src/mage/cards/i/IllicitAuction.java @@ -64,6 +64,8 @@ class IllicitAuctionEffect extends GainControlTargetEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); + Player controller = game.getPlayer(source.getControllerId()); Permanent targetCreature = game.getPermanent(source.getFirstTarget()); if (controller != null && targetCreature != null) { @@ -108,7 +110,6 @@ class IllicitAuctionEffect extends GainControlTargetEffect { winner.loseLife(highBid, game, source, false); super.controllingPlayerId = winner.getId(); } - super.init(source, game); } } diff --git a/Mage.Sets/src/mage/cards/i/IllicitMasquerade.java b/Mage.Sets/src/mage/cards/i/IllicitMasquerade.java new file mode 100644 index 00000000000..406e9c698b6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IllicitMasquerade.java @@ -0,0 +1,116 @@ +package mage.cards.i; + +import java.util.UUID; + +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FirstTargetPointer; + +/** + * + * @author DominionSpy + */ +public final class IllicitMasquerade extends CardImpl { + + public IllicitMasquerade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Illicit Masquerade enters the battlefield, put an impostor counter on each creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.IMPOSTOR.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE))); + + // Whenever a creature you control with an impostor counter on it dies, exile it. Return up to one other target creature card from your graveyard to the battlefield. + this.addAbility(new IllicitMasquerageAbility()); + } + + private IllicitMasquerade(final IllicitMasquerade card) { + super(card); + } + + @Override + public IllicitMasquerade copy() { + return new IllicitMasquerade(this); + } +} + +class IllicitMasquerageAbility extends DiesCreatureTriggeredAbility { + + private static final FilterPermanent filter1 = + new FilterCreaturePermanent("a creature you control with an impostor counter on it"); + private static final FilterCard filter2 = + new FilterCreatureCard("other target creature card from your graveyard"); + + static { + filter1.add(TargetController.YOU.getControllerPredicate()); + filter1.add(CounterType.IMPOSTOR.getPredicate()); + + filter2.add(IllicitMasqueradePredicate.instance); + } + + IllicitMasquerageAbility() { + super(new ExileTargetEffect().setText("exile it"), false, filter1, true); + this.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.addTarget(new TargetCardInYourGraveyard(0, 1, filter2)); + } + + private IllicitMasquerageAbility(final IllicitMasquerageAbility ability) { + super(ability); + } + + @Override + public IllicitMasquerageAbility copy() { + return new IllicitMasquerageAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + // DiesCreatureTriggeredAbility rewrites targets, so need to point the second effect + // back to the correct target. + getEffects().stream() + .filter(ReturnFromGraveyardToBattlefieldTargetEffect.class::isInstance) + .forEach(effect -> effect.setTargetPointer(new FirstTargetPointer())); + return true; + } + return false; + } +} + +enum IllicitMasqueradePredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Ability ability = input.getSource(); + if (!(ability instanceof TriggeredAbility)) { + return true; + } + GameEvent event = ((TriggeredAbility) ability).getTriggerEvent(); + return event == null || !input.getObject().getId().equals(event.getTargetId()); + } +} diff --git a/Mage.Sets/src/mage/cards/i/Illumination.java b/Mage.Sets/src/mage/cards/i/Illumination.java index 6081b1f439c..31280d5dbd6 100644 --- a/Mage.Sets/src/mage/cards/i/Illumination.java +++ b/Mage.Sets/src/mage/cards/i/Illumination.java @@ -80,7 +80,7 @@ class IlluminationEffect extends OneShotEffect { countered = true; } if (controller != null) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); int cost = spell.getManaValue(); Player player = game.getPlayer(spell.getControllerId()); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/i/Immerwolf.java b/Mage.Sets/src/mage/cards/i/Immerwolf.java index cc62359f98f..85299c15d76 100644 --- a/Mage.Sets/src/mage/cards/i/Immerwolf.java +++ b/Mage.Sets/src/mage/cards/i/Immerwolf.java @@ -12,7 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.Game; @@ -59,13 +59,13 @@ public final class Immerwolf extends CardImpl { class ImmerwolfEffect extends RestrictionEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.WEREWOLF); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.WEREWOLF); static { filter.add(Predicates.not(SubType.HUMAN.getPredicate())); } - public ImmerwolfEffect() { + ImmerwolfEffect() { super(Duration.WhileOnBattlefield); staticText = "Non-Human Werewolves you control can't transform"; } diff --git a/Mage.Sets/src/mage/cards/i/ImmolationShaman.java b/Mage.Sets/src/mage/cards/i/ImmolationShaman.java index df0a6f9ca77..36ec00e7d1b 100644 --- a/Mage.Sets/src/mage/cards/i/ImmolationShaman.java +++ b/Mage.Sets/src/mage/cards/i/ImmolationShaman.java @@ -70,6 +70,7 @@ class ImmolationShamanTriggeredAbility extends TriggeredAbilityImpl { ImmolationShamanTriggeredAbility() { super(Zone.BATTLEFIELD, new DamageTargetEffect(StaticValue.get(1), true, "that player", true)); + setTriggerPhrase("Whenever an opponent activates an ability of an artifact, creature, or land that isn't a mana ability, "); } private ImmolationShamanTriggeredAbility(final ImmolationShamanTriggeredAbility ability) { @@ -103,9 +104,4 @@ class ImmolationShamanTriggeredAbility extends TriggeredAbilityImpl { return false; } - @Override - public String getRule() { - return "Whenever an opponent activates an ability of an artifact, creature, or land on the battlefield, " + - "if it isn't a mana ability, {this} deals 1 damage to that player."; - } } diff --git a/Mage.Sets/src/mage/cards/i/ImotiCelebrantOfBounty.java b/Mage.Sets/src/mage/cards/i/ImotiCelebrantOfBounty.java index a9675a5bafa..7e5dab794e0 100644 --- a/Mage.Sets/src/mage/cards/i/ImotiCelebrantOfBounty.java +++ b/Mage.Sets/src/mage/cards/i/ImotiCelebrantOfBounty.java @@ -10,7 +10,7 @@ import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.mageobject.ManaValuePredicate; import java.util.UUID; @@ -20,7 +20,7 @@ import java.util.UUID; */ public final class ImotiCelebrantOfBounty extends CardImpl { - private static final FilterCard filter = new FilterCard("spells you cast with mana value 6 or greater"); + private static final FilterNonlandCard filter = new FilterNonlandCard("spells you cast with mana value 6 or greater"); static { filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 5)); diff --git a/Mage.Sets/src/mage/cards/i/ImpendingDisaster.java b/Mage.Sets/src/mage/cards/i/ImpendingDisaster.java index cd3ef30dec0..50f0e9d8c68 100644 --- a/Mage.Sets/src/mage/cards/i/ImpendingDisaster.java +++ b/Mage.Sets/src/mage/cards/i/ImpendingDisaster.java @@ -1,21 +1,21 @@ - package mage.cards.i; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ComparisonType; import mage.constants.TargetController; import mage.constants.Zone; -import mage.filter.common.FilterLandPermanent; -import mage.game.Game; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -23,16 +23,16 @@ import mage.game.Game; */ public final class ImpendingDisaster extends CardImpl { + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_LAND, ComparisonType.OR_GREATER, 7, false); + public ImpendingDisaster(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{R}"); - // At the beginning of your upkeep, if there are seven or more lands on the battlefield, sacrifice Impending Disaster and destroy all lands. TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new SacrificeSourceEffect(), TargetController.YOU, false); - ability.addEffect(new DestroyAllEffect(new FilterLandPermanent())); - ImpendingDisasterCondition contition = new ImpendingDisasterCondition(); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, contition, "At the beginning of your upkeep, if there are seven or more lands on the battlefield, sacrifice {this} and destroy all lands")); - + ability.addEffect(new DestroyAllEffect(StaticFilters.FILTER_LANDS)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, condition, + "At the beginning of your upkeep, if there are seven or more lands on the battlefield, sacrifice {this} and destroy all lands")); } private ImpendingDisaster(final ImpendingDisaster card) { @@ -43,12 +43,4 @@ public final class ImpendingDisaster extends CardImpl { public ImpendingDisaster copy() { return new ImpendingDisaster(this); } - - class ImpendingDisasterCondition implements Condition { - - @Override - public boolean apply(Game game, Ability source) { - return game.getBattlefield().count(new FilterLandPermanent(), source.getControllerId(), source, game) >= 7; - } - } } diff --git a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java index c4582594bff..d896db14b09 100644 --- a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java +++ b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java @@ -36,7 +36,7 @@ public final class ImperialHellkite extends CardImpl { // When Imperial Hellkite is turned face up, you may search your library for a Dragon card, reveal it, and put it into your hand. If you do, shuffle your library. Effect effect = new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 1, new FilterBySubtypeCard(SubType.DRAGON)), true); - effect.setText("you may search your library for a Dragon card, reveal it, and put it into your hand. If you do, shuffle"); + effect.setText("you may search your library for a Dragon card, reveal it, put it into your hand, then shuffle"); this.addAbility(new TurnedFaceUpSourceTriggeredAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/i/ImposterMech.java b/Mage.Sets/src/mage/cards/i/ImposterMech.java index 9db40b903ab..e9f72d35ea8 100644 --- a/Mage.Sets/src/mage/cards/i/ImposterMech.java +++ b/Mage.Sets/src/mage/cards/i/ImposterMech.java @@ -43,7 +43,7 @@ public final class ImposterMech extends CardImpl { this.addAbility(new EntersBattlefieldAbility( new CopyPermanentEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, applier), null, "You may have {this} enter the battlefield as a copy of a creature " + - "an opponent controls, except its a Vehicle artifact with crew 3 and it loses all other card types.", null + "an opponent controls, except it's a Vehicle artifact with crew 3 and it loses all other card types.", null )); // Crew 3 diff --git a/Mage.Sets/src/mage/cards/i/ImpulsiveManeuvers.java b/Mage.Sets/src/mage/cards/i/ImpulsiveManeuvers.java index ba4bf49e6ca..0d1effc5658 100644 --- a/Mage.Sets/src/mage/cards/i/ImpulsiveManeuvers.java +++ b/Mage.Sets/src/mage/cards/i/ImpulsiveManeuvers.java @@ -57,8 +57,8 @@ class ImpulsiveManeuversEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.wonFlip = game.getPlayer(source.getControllerId()).flipCoin(source, game, true); super.init(source, game); + this.wonFlip = game.getPlayer(source.getControllerId()).flipCoin(source, game, true); } @Override diff --git a/Mage.Sets/src/mage/cards/i/ImpulsiveWager.java b/Mage.Sets/src/mage/cards/i/ImpulsiveWager.java index 05539027827..a3dfd774a13 100644 --- a/Mage.Sets/src/mage/cards/i/ImpulsiveWager.java +++ b/Mage.Sets/src/mage/cards/i/ImpulsiveWager.java @@ -65,7 +65,7 @@ class ImpulsiveWagerEffect extends OneShotEffect { List cards = cost.getCards(); if (cards.size() == 1 && cards.get(0).isLand(game)) { Effect effect = new AddCountersTargetEffect(CounterType.BOUNTY.createInstance()); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } else { player.drawCards(2, source, game); diff --git a/Mage.Sets/src/mage/cards/i/InTheEyeOfChaos.java b/Mage.Sets/src/mage/cards/i/InTheEyeOfChaos.java index 54b86cd49c2..0fcf5a0df72 100644 --- a/Mage.Sets/src/mage/cards/i/InTheEyeOfChaos.java +++ b/Mage.Sets/src/mage/cards/i/InTheEyeOfChaos.java @@ -63,7 +63,7 @@ class InTheEyeOfChaosEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/i/InallaArchmageRitualist.java b/Mage.Sets/src/mage/cards/i/InallaArchmageRitualist.java index 97a614c4b4b..6ab64832e6d 100644 --- a/Mage.Sets/src/mage/cards/i/InallaArchmageRitualist.java +++ b/Mage.Sets/src/mage/cards/i/InallaArchmageRitualist.java @@ -112,7 +112,7 @@ class InallaArchmageRitualistEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, null, true); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect.apply(game, source)) { for (Permanent tokenPermanent : effect.getAddedPermanents()) { ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/i/InameLifeAspect.java b/Mage.Sets/src/mage/cards/i/InameLifeAspect.java index b542c4d903e..a09e3867000 100644 --- a/Mage.Sets/src/mage/cards/i/InameLifeAspect.java +++ b/Mage.Sets/src/mage/cards/i/InameLifeAspect.java @@ -80,7 +80,7 @@ class InameLifeAspectEffect extends OneShotEffect { if (controller != null && sourceObject != null) { if (controller.chooseUse(outcome, "Exile " + sourceObject.getLogName() + " to return Spirit cards?", source, game)) { Effect effect = new ReturnToHandTargetEffect(); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.getTargetPointer().init(game, source); new ExileSourceEffect().apply(game, source); return effect.apply(game, source); diff --git a/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java b/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java new file mode 100644 index 00000000000..862dec97d85 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java @@ -0,0 +1,103 @@ +package mage.cards.i; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllControlledTargetEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author DominionSpy + */ +public final class IncineratorOfTheGuilty extends CardImpl { + + public IncineratorOfTheGuilty(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Incinerator of the Guilty deals combat damage to a player, you may collect evidence X. + // When you do, Incinerator of the Guilty deals X damage to each creature and each planeswalker that player controls. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new IncineratorOfTheGuiltyEffect(), false, true)); + } + + private IncineratorOfTheGuilty(final IncineratorOfTheGuilty card) { + super(card); + } + + @Override + public IncineratorOfTheGuilty copy() { + return new IncineratorOfTheGuilty(this); + } +} + +class IncineratorOfTheGuiltyEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("creature and each planeswalker"); + + static { + filter.add( + Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate())); + } + + IncineratorOfTheGuiltyEffect() { + super(Outcome.Benefit); + staticText = "you may collect evidence X. When you do, {this} deals X damage " + + "to each creature and each planeswalker that player controls."; + } + + private IncineratorOfTheGuiltyEffect(final IncineratorOfTheGuiltyEffect effect) { + super(effect); + } + + @Override + public IncineratorOfTheGuiltyEffect copy() { + return new IncineratorOfTheGuiltyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.chooseUse(outcome, "Collect evidence X?", source, game)) { + return false; + } + + int xValue = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for X", game, source); + CollectEvidenceCost cost = new CollectEvidenceCost(xValue); + if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { + return false; + } + + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new DamageAllControlledTargetEffect(xValue, filter) + .setTargetPointer(getTargetPointer().copy()), false); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/Incite.java b/Mage.Sets/src/mage/cards/i/Incite.java index f3da767313c..55a3b041a19 100644 --- a/Mage.Sets/src/mage/cards/i/Incite.java +++ b/Mage.Sets/src/mage/cards/i/Incite.java @@ -1,5 +1,3 @@ - - package mage.cards.i; import java.util.UUID; @@ -24,7 +22,7 @@ public final class Incite extends CardImpl { // Target creature becomes red until end of turn and attacks this turn if able. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new BecomesColorTargetEffect(ObjectColor.RED, Duration.EndOfTurn, "Target creature becomes red until end of turn")); + this.getSpellAbility().addEffect(new BecomesColorTargetEffect(ObjectColor.RED, Duration.EndOfTurn)); this.getSpellAbility().addEffect(new AttacksIfAbleTargetEffect(Duration.EndOfTurn).setText("and attacks this turn if able")); } diff --git a/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java b/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java index 485da2d21cf..7d35d6be67a 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java @@ -62,7 +62,7 @@ class IncreasingSavageryEffect extends OneShotEffect { if (spell.getFromZone() == Zone.GRAVEYARD) { amount = 10; } - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.addCounters(CounterType.P1P1.createInstance(amount), source.getControllerId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java index 014d871bda0..5b33dd8191c 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java @@ -71,7 +71,7 @@ class IncreasingVengeanceEffect extends OneShotEffect { if (controller == null) { return false; } - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/i/IncrementalGrowth.java b/Mage.Sets/src/mage/cards/i/IncrementalGrowth.java index 882d19db818..96ca91cb9d1 100644 --- a/Mage.Sets/src/mage/cards/i/IncrementalGrowth.java +++ b/Mage.Sets/src/mage/cards/i/IncrementalGrowth.java @@ -33,7 +33,7 @@ public final class IncrementalGrowth extends CardImpl { target1.setTargetTag(1); this.getSpellAbility().addTarget(target1); - FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets two +1/+1 counter)"); + FilterCreaturePermanent filter2 = new FilterCreaturePermanent("another creature (gets two +1/+1 counters)"); filter2.add(new AnotherTargetPredicate(2)); TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter2); target2.setTargetTag(2); diff --git a/Mage.Sets/src/mage/cards/i/InduceDespair.java b/Mage.Sets/src/mage/cards/i/InduceDespair.java index dd21690f123..4a3a5dccef2 100644 --- a/Mage.Sets/src/mage/cards/i/InduceDespair.java +++ b/Mage.Sets/src/mage/cards/i/InduceDespair.java @@ -61,7 +61,7 @@ class InduceDespairEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { RevealTargetFromHandCost cost = (RevealTargetFromHandCost) source.getCosts().get(0); - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (cost != null && creature != null) { int cmcBoost = -1 * cost.manaValues; ContinuousEffect effect = new BoostTargetEffect(cmcBoost, cmcBoost, Duration.EndOfTurn); diff --git a/Mage.Sets/src/mage/cards/i/InduceParanoia.java b/Mage.Sets/src/mage/cards/i/InduceParanoia.java index d7c266852a1..53478fbefec 100644 --- a/Mage.Sets/src/mage/cards/i/InduceParanoia.java +++ b/Mage.Sets/src/mage/cards/i/InduceParanoia.java @@ -64,7 +64,7 @@ class InduceParanoiaEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { game.getStack().counter(spell.getId(), source, game); int spellCMC = spell.getManaValue(); diff --git a/Mage.Sets/src/mage/cards/i/InfernalOffering.java b/Mage.Sets/src/mage/cards/i/InfernalOffering.java index a36f58763e5..82412ed39ed 100644 --- a/Mage.Sets/src/mage/cards/i/InfernalOffering.java +++ b/Mage.Sets/src/mage/cards/i/InfernalOffering.java @@ -1,12 +1,5 @@ - package mage.cards.i; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -15,15 +8,18 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponent; +import mage.target.common.TargetSacrifice; + +import java.util.*; +import java.util.Map.Entry; /** * @@ -79,7 +75,7 @@ class InfernalOfferingSacrificeEffect extends OneShotEffect { Map toSacrifice = new HashMap<>(2); for (UUID playerId : game.getState().getPlayersInRange(player.getId(), game)) { if (playerId.equals(player.getId()) || playerId.equals(opponent.getId())) { - target = new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true); + target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); if (target.choose(Outcome.Sacrifice, playerId, source.getControllerId(), source, game)) { toSacrifice.put(playerId, target.getFirstTarget()); } diff --git a/Mage.Sets/src/mage/cards/i/Infuse.java b/Mage.Sets/src/mage/cards/i/Infuse.java index 55133d5fb68..b8e5d11acf5 100644 --- a/Mage.Sets/src/mage/cards/i/Infuse.java +++ b/Mage.Sets/src/mage/cards/i/Infuse.java @@ -36,7 +36,8 @@ public final class Infuse extends CardImpl { this.getSpellAbility().addTarget(new TargetPermanent(filter)); // Draw a card at the beginning of the next turn's upkeep. - this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility( + new DrawCardSourceControllerEffect(1)), false).concatBy("
    ")); } private Infuse(final Infuse card) { diff --git a/Mage.Sets/src/mage/cards/i/InheritedFiend.java b/Mage.Sets/src/mage/cards/i/InheritedFiend.java index 52f35e6004c..79afa9126fc 100644 --- a/Mage.Sets/src/mage/cards/i/InheritedFiend.java +++ b/Mage.Sets/src/mage/cards/i/InheritedFiend.java @@ -13,6 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -23,8 +24,6 @@ import java.util.UUID; */ public final class InheritedFiend extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public InheritedFiend(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); @@ -40,7 +39,7 @@ public final class InheritedFiend extends CardImpl { // {2}{B}: Exile target creature card from a graveyard. Put a +1/+1 counter on Inherited Fiend. Ability ability = new SimpleActivatedAbility(new ExileTargetEffect(), new ManaCostsImpl<>("{2}{B}")); ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy(".")); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/i/InnocentBystander.java b/Mage.Sets/src/mage/cards/i/InnocentBystander.java new file mode 100644 index 00000000000..f88a5cd37ac --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InnocentBystander.java @@ -0,0 +1,69 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class InnocentBystander extends CardImpl { + + public InnocentBystander(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever Innocent Bystander is dealt 3 or more damage, investigate. + this.addAbility(new InnocentBystanderTriggeredAbility()); + + } + + private InnocentBystander(final InnocentBystander card) { + super(card); + } + + @Override + public InnocentBystander copy() { + return new InnocentBystander(this); + } +} + +class InnocentBystanderTriggeredAbility extends TriggeredAbilityImpl { + + InnocentBystanderTriggeredAbility() { + super(Zone.BATTLEFIELD, new InvestigateEffect(), false); + setTriggerPhrase("Whenever {this} is dealt 3 or more damage, "); + } + + private InnocentBystanderTriggeredAbility(final InnocentBystanderTriggeredAbility ability) { + super(ability); + } + + @Override + public InnocentBystanderTriggeredAbility copy() { + return new InnocentBystanderTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getTargetId().equals(getSourceId()) && event.getAmount() >= 3; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InspiringRoar.java b/Mage.Sets/src/mage/cards/i/InspiringRoar.java index 46f80ce73e8..92af485b598 100644 --- a/Mage.Sets/src/mage/cards/i/InspiringRoar.java +++ b/Mage.Sets/src/mage/cards/i/InspiringRoar.java @@ -1,4 +1,3 @@ - package mage.cards.i; import java.util.UUID; @@ -7,7 +6,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -19,7 +18,7 @@ public final class InspiringRoar extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}"); // Put a +1/+1 counter on each creature you control. - getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent())); + getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE)); } private InspiringRoar(final InspiringRoar card) { diff --git a/Mage.Sets/src/mage/cards/i/InspiringStatuary.java b/Mage.Sets/src/mage/cards/i/InspiringStatuary.java index 6ece03d7daf..30a631bb751 100644 --- a/Mage.Sets/src/mage/cards/i/InspiringStatuary.java +++ b/Mage.Sets/src/mage/cards/i/InspiringStatuary.java @@ -7,7 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -18,11 +18,10 @@ import java.util.UUID; */ public final class InspiringStatuary extends CardImpl { - private static final FilterCard filter = new FilterCard("nonartifact spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("nonartifact spells you cast"); static { filter.add(Predicates.not(CardType.ARTIFACT.getPredicate())); - filter.add(Predicates.not(CardType.LAND.getPredicate())); filter.add(Predicates.not(new AbilityPredicate(ImproviseAbility.class))); // So there are not redundant copies being added to each card } diff --git a/Mage.Sets/src/mage/cards/i/IntiSeneschalOfTheSun.java b/Mage.Sets/src/mage/cards/i/IntiSeneschalOfTheSun.java index a801b3e110b..3ebf9c7aa5a 100644 --- a/Mage.Sets/src/mage/cards/i/IntiSeneschalOfTheSun.java +++ b/Mage.Sets/src/mage/cards/i/IntiSeneschalOfTheSun.java @@ -62,7 +62,8 @@ public final class IntiSeneschalOfTheSun extends CardImpl { class IntiSeneschalOfTheSunTriggeredAbility extends TriggeredAbilityImpl { IntiSeneschalOfTheSunTriggeredAbility() { - super(Zone.BATTLEFIELD, new ExileTopXMayPlayUntilEffect(1, Duration.UntilYourNextEndStep)); + super(Zone.BATTLEFIELD, new ExileTopXMayPlayUntilEffect(1, Duration.UntilYourNextEndStep) + .withTextOptions("that card", true)); this.setTriggerPhrase("Whenever you discard one or more cards, "); } diff --git a/Mage.Sets/src/mage/cards/i/IntrepidProvisioner.java b/Mage.Sets/src/mage/cards/i/IntrepidProvisioner.java index 844e2edccc1..d5106556afa 100644 --- a/Mage.Sets/src/mage/cards/i/IntrepidProvisioner.java +++ b/Mage.Sets/src/mage/cards/i/IntrepidProvisioner.java @@ -1,4 +1,3 @@ - package mage.cards.i; import java.util.UUID; @@ -12,9 +11,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.TargetPermanent; /** * @@ -22,7 +21,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class IntrepidProvisioner extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.HUMAN, "another target Human you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "another target Human you control"); static { filter.add(AnotherPredicate.instance); @@ -39,7 +38,7 @@ public final class IntrepidProvisioner extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // When Intrepid Provisioner enters the battlefield, another target Human you control gets +2/+2 until end of turn. Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(2, 2, Duration.EndOfTurn), false); - ability.addTarget(new TargetControlledCreaturePermanent(filter)); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java new file mode 100644 index 00000000000..bf55022d140 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java @@ -0,0 +1,161 @@ +package mage.cards.i; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.Thopter00ColorlessToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; + +/** + * + * @author DominionSpy + */ +public final class IntrudeOnTheMind extends CardImpl { + + public IntrudeOnTheMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); + + // Reveal the top five cards of your library and separate them into two piles. An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard. + // Create a 0/0 colorless Thopter artifact creature token with flying, then put a +1/+1 counter on it for each card put into your graveyard this way. + this.getSpellAbility().addEffect(new IntrudeOnTheMindEffect()); + } + + private IntrudeOnTheMind(final IntrudeOnTheMind card) { + super(card); + } + + @Override + public IntrudeOnTheMind copy() { + return new IntrudeOnTheMind(this); + } +} + +class IntrudeOnTheMindEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("cards to put in the first pile"); + + IntrudeOnTheMindEffect() { + super(Outcome.DrawCard); + staticText = "Reveal the top five cards of your library and separate them into two piles. " + + "An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard. " + + "Create a 0/0 colorless Thopter artifact creature token with flying, " + + "then put a +1/+1 counter on it for each card put into your graveyard this way."; + } + + private IntrudeOnTheMindEffect(final IntrudeOnTheMindEffect effect) { + super(effect); + } + + @Override + public IntrudeOnTheMindEffect copy() { + return new IntrudeOnTheMindEffect(this); + } + + /** + * Pile-choosing logic based on {@link mage.abilities.effects.common.RevealAndSeparatePilesEffect}. + */ + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Cards allCards = new CardsImpl(controller.getLibrary().getTopCards(game, 5)); + Cards cards = allCards.copy(); + controller.revealCards(source, cards, game); + + TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, filter); + List pile1 = new ArrayList<>(); + controller.choose(Outcome.Neutral, cards, target, source, game); + target.getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .forEach(pile1::add); + cards.removeIf(target.getTargets()::contains); + List pile2 = new ArrayList<>(); + pile2.addAll(cards.getCards(game)); + + Player opponent = getOpponent(controller, game, source); + if (opponent == null) { + return false; + } + boolean choice = opponent.choosePile(outcome, "Choose a pile to put into hand.", pile1, pile2, game); + + Zone pile1Zone = choice ? Zone.HAND : Zone.GRAVEYARD; + Zone pile2Zone = choice ? Zone.GRAVEYARD : Zone.HAND; + + game.informPlayers("Pile 1, going to " + pile1Zone + ": " + (pile1.isEmpty() ? " (none)" : + pile1.stream().map(MageObject::getName).collect(Collectors.joining(", ")))); + cards.clear(); + cards.addAllCards(pile1); + controller.moveCards(cards, pile1Zone, source, game); + + game.informPlayers("Pile 2, going to " + pile2Zone + ": " + (pile2.isEmpty() ? " (none)" : + pile2.stream().map(MageObject::getName).collect(Collectors.joining(", ")))); + cards.clear(); + cards.addAllCards(pile2); + controller.moveCards(cards, pile2Zone, source, game); + + Token token = new Thopter00ColorlessToken(); + token.putOntoBattlefield(1, game, source); + + allCards.retainZone(Zone.GRAVEYARD, game); + int count = allCards.size(); + if (count <= 0) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Permanent permanent = game.getPermanent(tokenId); + if (permanent == null) { + continue; + } + permanent.addCounters(CounterType.P1P1.createInstance(count), source.getControllerId(), source, game); + } + return true; + } + + private static Player getOpponent(Player controller, Game game, Ability source) { + Player opponent; + Set opponents = game.getOpponents(source.getControllerId()); + if (opponents.isEmpty()) { + return null; + } + if (opponents.size() == 1) { + opponent = game.getPlayer(opponents.iterator().next()); + } else { + Target targetOpponent = new TargetOpponent(true); + controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game); + opponent = game.getPlayer(targetOpponent.getFirstTarget()); + if (opponent == null) { + return null; + } + game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose the piles"); + } + return opponent; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvaderParasite.java b/Mage.Sets/src/mage/cards/i/InvaderParasite.java index 435bbc1922e..16bf0109e77 100644 --- a/Mage.Sets/src/mage/cards/i/InvaderParasite.java +++ b/Mage.Sets/src/mage/cards/i/InvaderParasite.java @@ -65,7 +65,7 @@ class InvaderParasiteImprintEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (sourcePermanent != null && targetPermanent != null) { targetPermanent.moveToExile(getId(), "Invader Parasite (Imprint)", source, game); sourcePermanent.imprint(targetPermanent.getId(), game); diff --git a/Mage.Sets/src/mage/cards/i/InvertInvent.java b/Mage.Sets/src/mage/cards/i/InvertInvent.java index 3fe1e6e40e0..e6e41bf2a39 100644 --- a/Mage.Sets/src/mage/cards/i/InvertInvent.java +++ b/Mage.Sets/src/mage/cards/i/InvertInvent.java @@ -66,7 +66,7 @@ class InvertEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { ContinuousEffect effect = new SwitchPowerToughnessTargetEffect(Duration.EndOfTurn); effect.setTargetPointer(new FixedTarget(targetId, game)); game.addEffect(effect, source); diff --git a/Mage.Sets/src/mage/cards/i/InvigoratingSurge.java b/Mage.Sets/src/mage/cards/i/InvigoratingSurge.java index 3857c24478b..0ffb169e787 100644 --- a/Mage.Sets/src/mage/cards/i/InvigoratingSurge.java +++ b/Mage.Sets/src/mage/cards/i/InvigoratingSurge.java @@ -1,15 +1,11 @@ package mage.cards.i; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -24,7 +20,9 @@ public final class InvigoratingSurge extends CardImpl { // Put a +1/+1 counter on target creature you control, then double the number of +1/+1 counters on that creature. this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); - this.getSpellAbility().addEffect(new InvigoratingSurgeEffect()); + this.getSpellAbility().addEffect(new DoubleCountersTargetEffect(CounterType.P1P1) + .setText(", then double the number of +1/+1 counters on that creature") + ); this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); } @@ -37,30 +35,3 @@ public final class InvigoratingSurge extends CardImpl { return new InvigoratingSurge(this); } } - -class InvigoratingSurgeEffect extends OneShotEffect { - - InvigoratingSurgeEffect() { - super(Outcome.Benefit); - staticText = ", then double the number of +1/+1 counters on that creature"; - } - - private InvigoratingSurgeEffect(final InvigoratingSurgeEffect effect) { - super(effect); - } - - @Override - public InvigoratingSurgeEffect copy() { - return new InvigoratingSurgeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent == null) { - return false; - } - int counterCount = permanent.getCounters(game).getCount(CounterType.P1P1); - return counterCount > 0 && permanent.addCounters(CounterType.P1P1.createInstance(counterCount), source.getControllerId(), source, game); - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/i/InvokePrejudice.java b/Mage.Sets/src/mage/cards/i/InvokePrejudice.java index c8fca3d3afb..b6500d31588 100644 --- a/Mage.Sets/src/mage/cards/i/InvokePrejudice.java +++ b/Mage.Sets/src/mage/cards/i/InvokePrejudice.java @@ -110,7 +110,7 @@ class InvokePrejudiceEffect extends CounterUnlessPaysEffect { Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { CounterUnlessPaysEffect effect = new CounterUnlessPaysEffect(new GenericManaCost(spell.getManaValue())); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); result = effect.apply(game, source); } return result; diff --git a/Mage.Sets/src/mage/cards/i/InzervaMasterOfInsights.java b/Mage.Sets/src/mage/cards/i/InzervaMasterOfInsights.java new file mode 100644 index 00000000000..7a02e65e87e --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InzervaMasterOfInsights.java @@ -0,0 +1,100 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.command.emblems.InzervaMasterOfInsightsEmblem; +import mage.players.Player; +import mage.target.TargetCard; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class InzervaMasterOfInsights extends CardImpl { + + public InzervaMasterOfInsights(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{2/U}{2/R}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.INZERVA); + this.setStartingLoyalty(4); + + // +2: Draw two cards, then discard a card. + this.addAbility(new LoyaltyAbility(new DrawDiscardControllerEffect(2, 1), 2)); + + // −2: Look at the top two cards of each other player's library, then put any number of them on the bottom of that library and the rest on top in any order. Scry 2. + LoyaltyAbility ability = new LoyaltyAbility(new InzervaMasterOfInsightsEffect(), -2); + ability.addEffect(new ScryEffect(2, false)); + this.addAbility(ability); + + // −4: You get an emblem with "Your opponents play with their hands revealed" and "Whenever an opponent draws a card, this emblem deals 1 damage to them." + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new InzervaMasterOfInsightsEmblem()), -4)); + } + + private InzervaMasterOfInsights(final InzervaMasterOfInsights card) { + super(card); + } + + @Override + public InzervaMasterOfInsights copy() { + return new InzervaMasterOfInsights(this); + } +} + +class InzervaMasterOfInsightsEffect extends OneShotEffect { + + InzervaMasterOfInsightsEffect() { + super(Outcome.Benefit); + staticText = "Look at the top two cards of each other player's library, then put any number of them on the bottom of that library and the rest on top in any order"; + } + + private InzervaMasterOfInsightsEffect(final InzervaMasterOfInsightsEffect effect) { + super(effect); + } + + @Override + public InzervaMasterOfInsightsEffect copy() { + return new InzervaMasterOfInsightsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player opponent = game.getPlayer(playerId); + if (playerId.equals(controller.getId()) || opponent == null) { + continue; + } + Cards cards = new CardsImpl(); + int count = Math.min(2, opponent.getLibrary().size()); + if (count == 0) { + continue; + } + for (int i = 0; i < count; i++) { + Card card = opponent.getLibrary().removeFromTop(game); + cards.add(card); + } + TargetCard targets = new TargetCard(0, cards.size(), Zone.LIBRARY, new FilterCard("cards to PUT on the BOTTOM of " + opponent.getName() + "'s library")); + controller.chooseTarget(Outcome.Neutral, cards, targets, source, game); + if (!controller.canRespond() || !opponent.canRespond()) { + continue; + } + controller.putCardsOnBottomOfLibrary(new CardsImpl(targets.getTargets()), game, source, true); + cards.removeIf(targets.getTargets()::contains); + + controller.putCardsOnTopOfLibrary(cards, game, source, true); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/i/IreOfKaminari.java b/Mage.Sets/src/mage/cards/i/IreOfKaminari.java index 02bd3d768cd..573ee91744a 100644 --- a/Mage.Sets/src/mage/cards/i/IreOfKaminari.java +++ b/Mage.Sets/src/mage/cards/i/IreOfKaminari.java @@ -29,7 +29,8 @@ public final class IreOfKaminari extends CardImpl { this.subtype.add(SubType.ARCANE); // Ire of Kaminari deals damage to any target equal to the number of Arcane cards in your graveyard. - this.getSpellAbility().addEffect(new DamageTargetEffect(new CardsInControllerGraveyardCount(filter))); + this.getSpellAbility().addEffect(new DamageTargetEffect(new CardsInControllerGraveyardCount(filter)) + .setText("{this} deals damage to any target equal to the number of Arcane cards in your graveyard")); this.getSpellAbility().addTarget(new TargetAnyTarget()); } diff --git a/Mage.Sets/src/mage/cards/i/IronMaiden.java b/Mage.Sets/src/mage/cards/i/IronMaiden.java index db3611c4aa7..5a7fa900937 100644 --- a/Mage.Sets/src/mage/cards/i/IronMaiden.java +++ b/Mage.Sets/src/mage/cards/i/IronMaiden.java @@ -50,7 +50,7 @@ class IronMaidenEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int amount = player.getHand().size() - 4; if (amount > 0) { diff --git a/Mage.Sets/src/mage/cards/i/IronMastiff.java b/Mage.Sets/src/mage/cards/i/IronMastiff.java index 79145d0241c..75a41b5ac77 100644 --- a/Mage.Sets/src/mage/cards/i/IronMastiff.java +++ b/Mage.Sets/src/mage/cards/i/IronMastiff.java @@ -13,6 +13,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; +import mage.game.combat.CombatGroup; import mage.players.Player; import java.util.Objects; @@ -81,8 +82,11 @@ class IronMastiffEffect extends RollDieWithResultTableEffect { } int toRoll = game .getCombat() - .getDefenders() + .getGroups() .stream() + .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) + .distinct() .map(game::getPlayer) .filter(Objects::nonNull) .mapToInt(x -> 1) diff --git a/Mage.Sets/src/mage/cards/i/IshkanahGrafwidow.java b/Mage.Sets/src/mage/cards/i/IshkanahGrafwidow.java index 8f3590dea72..63ae68c7143 100644 --- a/Mage.Sets/src/mage/cards/i/IshkanahGrafwidow.java +++ b/Mage.Sets/src/mage/cards/i/IshkanahGrafwidow.java @@ -19,7 +19,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SpiderToken; import mage.target.common.TargetOpponent; @@ -30,11 +30,7 @@ import java.util.UUID; */ public final class IshkanahGrafwidow extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Spider you control"); - - static { - filter.add(SubType.SPIDER.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SPIDER, "Spider you control"); public IshkanahGrafwidow(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); diff --git a/Mage.Sets/src/mage/cards/i/IsolationCell.java b/Mage.Sets/src/mage/cards/i/IsolationCell.java index 9fafc407551..71d68317deb 100644 --- a/Mage.Sets/src/mage/cards/i/IsolationCell.java +++ b/Mage.Sets/src/mage/cards/i/IsolationCell.java @@ -97,7 +97,7 @@ class IsolationCellEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Cost cost = ManaUtil.createManaCost(2, false); if (!cost.pay(source, game, source, player.getId(), false)) { diff --git a/Mage.Sets/src/mage/cards/i/ItDoesntAddUp.java b/Mage.Sets/src/mage/cards/i/ItDoesntAddUp.java new file mode 100644 index 00000000000..5ed94ee01fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/ItDoesntAddUp.java @@ -0,0 +1,70 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ItDoesntAddUp extends CardImpl { + + public ItDoesntAddUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}{B}"); + + // Return target creature card from your graveyard to the battlefield. Suspect it. + this.getSpellAbility().addEffect(new ItDoesntAddUpEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + } + + private ItDoesntAddUp(final ItDoesntAddUp card) { + super(card); + } + + @Override + public ItDoesntAddUp copy() { + return new ItDoesntAddUp(this); + } +} + +class ItDoesntAddUpEffect extends OneShotEffect { + + ItDoesntAddUpEffect() { + super(Outcome.Benefit); + staticText = "return target creature card from your graveyard to the battlefield. Suspect it"; + } + + private ItDoesntAddUpEffect(final ItDoesntAddUpEffect effect) { + super(effect); + } + + @Override + public ItDoesntAddUpEffect copy() { + return new ItDoesntAddUpEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.BATTLEFIELD, source, game); + Optional.ofNullable(game.getPermanent(card.getId())) + .ifPresent(permanent -> permanent.setSuspected(true, game, source)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/ItThatHeraldsTheEnd.java b/Mage.Sets/src/mage/cards/i/ItThatHeraldsTheEnd.java new file mode 100644 index 00000000000..5e1d40dfa1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/ItThatHeraldsTheEnd.java @@ -0,0 +1,59 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorlessPredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ItThatHeraldsTheEnd extends CardImpl { + + private static final FilterCard filter = new FilterCard("colorless spells you cast with mana value 7 or greater"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("colorless creatures"); + + static { + filter.add(ColorlessPredicate.instance); + filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 6)); + filter2.add(ColorlessPredicate.instance); + } + + public ItThatHeraldsTheEnd(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{C}"); + + this.subtype.add(SubType.ELDRAZI); + this.subtype.add(SubType.DRONE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Colorless spells you cast with mana value 7 or greater cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionAllEffect(filter, 1))); + + // Other colorless creatures you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 1, 1, Duration.WhileOnBattlefield, filter2, true + ))); + } + + private ItThatHeraldsTheEnd(final ItThatHeraldsTheEnd card) { + super(card); + } + + @Override + public ItThatHeraldsTheEnd copy() { + return new ItThatHeraldsTheEnd(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IvoryGargoyle.java b/Mage.Sets/src/mage/cards/i/IvoryGargoyle.java index d3169022f7a..da0d3c9565d 100644 --- a/Mage.Sets/src/mage/cards/i/IvoryGargoyle.java +++ b/Mage.Sets/src/mage/cards/i/IvoryGargoyle.java @@ -37,7 +37,7 @@ public final class IvoryGargoyle extends CardImpl { Ability ability = new DiesSourceTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(false, true) .setText("return it to the battlefield under its owner's control at the beginning of the next end step") - ).setTriggerPhrase(""))); + ).setTriggerPhrase("")).setText("return it to the battlefield under its owner's control at the beginning of the next end step")); ability.addEffect(new SkipNextDrawStepControllerEffect().concatBy("and")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java b/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java index 5f5b904bfdf..515d80034a6 100644 --- a/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java +++ b/Mage.Sets/src/mage/cards/i/IzoniCenterOfTheWeb.java @@ -43,7 +43,7 @@ public final class IzoniCenterOfTheWeb extends CardImpl { this.toughness = new MageInt(4); // Menace - this.addAbility(new MenaceAbility()); + this.addAbility(new MenaceAbility(false)); // Whenever Izoni, Center of the Web enters the battlefield or attacks, you may collect evidence 4. If you do, create two 2/1 black and green Spider creature tokens with menace and reach. this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DoIfCostPaid( diff --git a/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java b/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java index d6c5c7a0964..47b212807e1 100644 --- a/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java +++ b/Mage.Sets/src/mage/cards/i/IzzetStaticaster.java @@ -76,7 +76,7 @@ class IzzetStaticasterDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); if (CardUtil.haveEmptyName(targetPermanent)) { diff --git a/Mage.Sets/src/mage/cards/j/JaceMemoryAdept.java b/Mage.Sets/src/mage/cards/j/JaceMemoryAdept.java index 6d606770239..18926d7f61c 100644 --- a/Mage.Sets/src/mage/cards/j/JaceMemoryAdept.java +++ b/Mage.Sets/src/mage/cards/j/JaceMemoryAdept.java @@ -68,7 +68,7 @@ class JaceMemoryAdeptEffect extends DrawCardTargetEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID target : targetPointer.getTargets(game, source)) { + for (UUID target : getTargetPointer().getTargets(game, source)) { Player player = game.getPlayer(target); if (player != null) { player.drawCards(amount.calculate(game, source, this), source, game); diff --git a/Mage.Sets/src/mage/cards/j/JadeBearer.java b/Mage.Sets/src/mage/cards/j/JadeBearer.java index 98c977b51a2..0ad5ed2a359 100644 --- a/Mage.Sets/src/mage/cards/j/JadeBearer.java +++ b/Mage.Sets/src/mage/cards/j/JadeBearer.java @@ -9,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; @@ -19,8 +19,8 @@ import mage.target.TargetPermanent; */ public final class JadeBearer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = - new FilterControlledCreaturePermanent(SubType.MERFOLK, "another target Merfolk you control"); + private static final FilterControlledPermanent filter = + new FilterControlledPermanent(SubType.MERFOLK, "another target Merfolk you control"); static { filter.add(AnotherPredicate.instance); } diff --git a/Mage.Sets/src/mage/cards/j/JadeGuardian.java b/Mage.Sets/src/mage/cards/j/JadeGuardian.java index 87f1d77a0cd..e0fcdd855c7 100644 --- a/Mage.Sets/src/mage/cards/j/JadeGuardian.java +++ b/Mage.Sets/src/mage/cards/j/JadeGuardian.java @@ -1,4 +1,3 @@ - package mage.cards.j; import java.util.UUID; @@ -12,8 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -21,6 +20,8 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class JadeGuardian extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.MERFOLK, "Merfolk you control"); + public JadeGuardian(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); @@ -34,7 +35,7 @@ public final class JadeGuardian extends CardImpl { // When Jade Guardian enters the battlefield, put a +1/+1 counter on target Merfolk you control. Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); - ability.addTarget(new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent(SubType.MERFOLK, "Merfolk you control"))); + ability.addTarget(new TargetControlledPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/j/JadeMonolith.java b/Mage.Sets/src/mage/cards/j/JadeMonolith.java index 5eba05dcebe..2f992ce9092 100644 --- a/Mage.Sets/src/mage/cards/j/JadeMonolith.java +++ b/Mage.Sets/src/mage/cards/j/JadeMonolith.java @@ -70,6 +70,7 @@ class JadeMonolithRedirectionEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/j/JarJarBinks.java b/Mage.Sets/src/mage/cards/j/JarJarBinks.java index d0e5f5d524e..f3e78ab13ce 100644 --- a/Mage.Sets/src/mage/cards/j/JarJarBinks.java +++ b/Mage.Sets/src/mage/cards/j/JarJarBinks.java @@ -80,7 +80,7 @@ class JarJarBinksEffect extends OneShotEffect { Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null && jarJar != null && opponent != null) { ContinuousEffect effect = new JarJarBinksGainControlSourceEffect(); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); game.informPlayers(jarJar.getName() + " is now controlled by " + opponent.getLogName()); return true; diff --git a/Mage.Sets/src/mage/cards/j/JaredCarthalion.java b/Mage.Sets/src/mage/cards/j/JaredCarthalion.java index a3843982a38..01e051d72f9 100644 --- a/Mage.Sets/src/mage/cards/j/JaredCarthalion.java +++ b/Mage.Sets/src/mage/cards/j/JaredCarthalion.java @@ -118,7 +118,7 @@ class JaredCarthalionUltimateEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && card != null) { if (controller.moveCards(card, Zone.HAND, source, game) && card.getColor().getColorCount() == 5) { diff --git a/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java b/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java index 22fe9e4edd0..c2475cec878 100644 --- a/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java +++ b/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java @@ -136,7 +136,7 @@ class JeskaThriceRebornEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (!((DamagePlayerEvent) event).isCombatDamage() - || !event.getSourceId().equals(targetPointer.getFirst(game, source))) { + || !event.getSourceId().equals(getTargetPointer().getFirst(game, source))) { return false; } Player player = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/j/JeskaiCharm.java b/Mage.Sets/src/mage/cards/j/JeskaiCharm.java index ed4c88c3320..dceac02d82e 100644 --- a/Mage.Sets/src/mage/cards/j/JeskaiCharm.java +++ b/Mage.Sets/src/mage/cards/j/JeskaiCharm.java @@ -13,7 +13,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetOpponentOrPlaneswalker; @@ -38,7 +38,7 @@ public final class JeskaiCharm extends CardImpl { Effect effect = new BoostControlledEffect(1, 1, Duration.EndOfTurn); effect.setText("Creatures you control get +1/+1"); mode = new Mode(effect); - effect = new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent()); + effect = new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURE); effect.setText("and gain lifelink until end of turn"); mode.addEffect(effect); this.getSpellAbility().addMode(mode); diff --git a/Mage.Sets/src/mage/cards/j/JeskaiInfiltrator.java b/Mage.Sets/src/mage/cards/j/JeskaiInfiltrator.java index 632b1ca6b04..58679c1649f 100644 --- a/Mage.Sets/src/mage/cards/j/JeskaiInfiltrator.java +++ b/Mage.Sets/src/mage/cards/j/JeskaiInfiltrator.java @@ -1,36 +1,25 @@ - package mage.cards.j; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CreatureCountCondition; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType; +import mage.abilities.effects.keyword.ManifestEffect; import mage.cards.Card; 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.constants.TargetController; -import mage.constants.Zone; -import mage.game.ExileZone; +import mage.constants.*; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.*; + /** * * @author emerald000 @@ -82,35 +71,30 @@ class JeskaiInfiltratorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Set cardsToManifest = new HashSet<>(); - cardsToManifest.add(source.getSourcePermanentIfItStillExists(game)); - cardsToManifest.add(controller.getLibrary().getFromTop(game)); - UUID exileId = UUID.randomUUID(); - controller.moveCardsToExile(cardsToManifest, source, game, false, exileId, ""); - ExileZone exileZone = game.getExile().getExileZone(exileId); - for (Card card : exileZone.getCards(game)) { - card.setFaceDown(true, game); - } - game.fireUpdatePlayersEvent(); // removes Jeskai Infiltrator from Battlefield, so Jeskai Infiltrator returns as a fresh permanent to the battlefield with new position - - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - //the Set will mimic the Shuffling - exileZone.getCards(game).forEach(card -> { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); - }); - controller.moveCards(exileZone.getCards(game), Zone.BATTLEFIELD, source, game, false, true, false, null); - return true; + if (controller == null) { + return false; } - return false; + UUID exileId = UUID.randomUUID(); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (sourcePermanent != null) { + controller.moveCardsToExile(sourcePermanent, source, game, false, exileId, ""); + } + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard != null) { + controller.moveCardsToExile(topCard, source, game, false, exileId, ""); + } + // need to get source permanent as card rather than permanent for next steps, hence this convoluted code + List cardsToManifest = new ArrayList<>(game.getExile().getExileZone(exileId).getCards(game)); + if (cardsToManifest.isEmpty()) { + return false; + } + for (Card card : cardsToManifest) { + card.setFaceDown(true, game); + } + Collections.shuffle(cardsToManifest); + game.informPlayers(controller.getLogName() + " shuffles the face-down pile"); + game.getState().processAction(game); + ManifestEffect.doManifestCards(game, source, controller, new LinkedHashSet<>(cardsToManifest)); + return true; } } diff --git a/Mage.Sets/src/mage/cards/j/JeskasWill.java b/Mage.Sets/src/mage/cards/j/JeskasWill.java index f3856429ded..7cde1f4968b 100644 --- a/Mage.Sets/src/mage/cards/j/JeskasWill.java +++ b/Mage.Sets/src/mage/cards/j/JeskasWill.java @@ -27,7 +27,7 @@ public final class JeskasWill extends CardImpl { // Choose one. If you control a commander as you cast this spell, you may choose both. this.getSpellAbility().getModes().setChooseText( - "Choose one. If you control a commander as you cast this spell, you may choose both." + "Choose one. If you control a commander as you cast this spell, you may choose both instead." ); this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); @@ -70,7 +70,7 @@ class JeskasWillEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(source.getFirstTarget()); - if (controller == null || player == null || player.getHand().size() < 1) { + if (controller == null || player == null || player.getHand().isEmpty()) { return false; } controller.getManaPool().addMana(Mana.RedMana(player.getHand().size()), game, source); diff --git a/Mage.Sets/src/mage/cards/j/JestersScepter.java b/Mage.Sets/src/mage/cards/j/JestersScepter.java index 07486c3de1a..ae56998df61 100644 --- a/Mage.Sets/src/mage/cards/j/JestersScepter.java +++ b/Mage.Sets/src/mage/cards/j/JestersScepter.java @@ -204,13 +204,13 @@ class JestersScepterCounterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { String nameOfExiledCardPayment = (String) game.getState().getValue(source.getSourceId() + "_nameOfExiledCardPayment"); String nameOfExiledCardPayment2 = (String) game.getState().getValue(source.getSourceId() + "_nameOfExiledCardPayment2"); if (CardUtil.haveSameNames(spell.getCard(), nameOfExiledCardPayment, game) || CardUtil.haveSameNames(spell.getCard(), nameOfExiledCardPayment2, game)) { - return game.getStack().counter(targetPointer.getFirst(game, source), source, game); + return game.getStack().counter(getTargetPointer().getFirst(game, source), source, game); } } return false; diff --git a/Mage.Sets/src/mage/cards/j/JinxedChoker.java b/Mage.Sets/src/mage/cards/j/JinxedChoker.java index 04bc95f5db2..157058654e9 100644 --- a/Mage.Sets/src/mage/cards/j/JinxedChoker.java +++ b/Mage.Sets/src/mage/cards/j/JinxedChoker.java @@ -6,10 +6,10 @@ import mage.abilities.common.OnEventTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.cards.CardImpl; @@ -34,7 +34,7 @@ public final class JinxedChoker extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // At the beginning of your end step, target opponent gains control of Jinxed Choker and puts a charge counter on it. - Ability endStepAbility = new BeginningOfYourEndStepTriggeredAbility(new JinxedChokerChangeControllerEffect(), false); + Ability endStepAbility = new BeginningOfYourEndStepTriggeredAbility(new TargetPlayerGainControlSourceEffect(), false); endStepAbility.addEffect(new JinxedChokerAddCounterEffect()); endStepAbility.addTarget(new TargetOpponent()); this.addAbility(endStepAbility); @@ -58,35 +58,6 @@ public final class JinxedChoker extends CardImpl { } } -class JinxedChokerChangeControllerEffect extends ContinuousEffectImpl { - - JinxedChokerChangeControllerEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "target opponent gains control of {this}"; - } - - private JinxedChokerChangeControllerEffect(final JinxedChokerChangeControllerEffect effect) { - super(effect); - } - - @Override - public JinxedChokerChangeControllerEffect copy() { - return new JinxedChokerChangeControllerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } - -} - class JinxedChokerAddCounterEffect extends OneShotEffect { JinxedChokerAddCounterEffect() { diff --git a/Mage.Sets/src/mage/cards/j/JinxedIdol.java b/Mage.Sets/src/mage/cards/j/JinxedIdol.java index 03f4af2832b..8a9400371f2 100644 --- a/Mage.Sets/src/mage/cards/j/JinxedIdol.java +++ b/Mage.Sets/src/mage/cards/j/JinxedIdol.java @@ -1,23 +1,21 @@ - package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.OnEventTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author BetaSteward_at_googlemail.com @@ -28,10 +26,10 @@ public final class JinxedIdol extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // At the beginning of your upkeep, Jinxed Idol deals 2 damage to you. - this.addAbility(new OnEventTriggeredAbility(EventType.UPKEEP_STEP_PRE, "beginning of your upkeep", new DamageControllerEffect(2))); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DamageControllerEffect(2), TargetController.YOU, false)); // Sacrifice a creature: Target opponent gains control of Jinxed Idol. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new JinxedIdolEffect(), + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TargetPlayerGainControlSourceEffect(), new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)); ability.addTarget(new TargetOpponent()); this.addAbility(ability); @@ -47,32 +45,3 @@ public final class JinxedIdol extends CardImpl { } } - -class JinxedIdolEffect extends ContinuousEffectImpl { - - JinxedIdolEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "Target opponent gains control of {this}"; - } - - private JinxedIdolEffect(final JinxedIdolEffect effect) { - super(effect); - } - - @Override - public JinxedIdolEffect copy() { - return new JinxedIdolEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/j/JinxedRing.java b/Mage.Sets/src/mage/cards/j/JinxedRing.java index d1113f68ab9..8431b4cd0c9 100644 --- a/Mage.Sets/src/mage/cards/j/JinxedRing.java +++ b/Mage.Sets/src/mage/cards/j/JinxedRing.java @@ -1,30 +1,22 @@ - package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; - import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author fireshoes @@ -44,7 +36,7 @@ public final class JinxedRing extends CardImpl { this.addAbility(new PutIntoGraveFromBattlefieldAllTriggeredAbility(new DamageControllerEffect(1), false, filter, false, true)); // Sacrifice a creature: Target opponent gains control of Jinxed Ring. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new JinxedRingEffect(), + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TargetPlayerGainControlSourceEffect(), new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)); ability.addTarget(new TargetOpponent()); this.addAbility(ability); @@ -59,32 +51,3 @@ public final class JinxedRing extends CardImpl { return new JinxedRing(this); } } - -class JinxedRingEffect extends ContinuousEffectImpl { - - JinxedRingEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "Target opponent gains control of {this}"; - } - - private JinxedRingEffect(final JinxedRingEffect effect) { - super(effect); - } - - @Override - public JinxedRingEffect copy() { - return new JinxedRingEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java b/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java index 2773fdb5864..227cb3bcded 100644 --- a/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java +++ b/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java @@ -160,6 +160,6 @@ class JohannApprenticeSorcererWatcher extends Watcher { } public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) { - return usedFrom.getOrDefault(playerId, new HashSet<>()).contains(mor); + return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor); } } diff --git a/Mage.Sets/src/mage/cards/j/JointAssault.java b/Mage.Sets/src/mage/cards/j/JointAssault.java index bcb2db7e2d4..74f55cd692e 100644 --- a/Mage.Sets/src/mage/cards/j/JointAssault.java +++ b/Mage.Sets/src/mage/cards/j/JointAssault.java @@ -66,7 +66,7 @@ class JointAssaultBoostTargetEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); - UUID permanentId = targetPointer.getFirst(game, source); + UUID permanentId = getTargetPointer().getFirst(game, source); Permanent target = game.getPermanent(permanentId); if (target != null) { if (target.getPairedCard() != null) { @@ -78,7 +78,7 @@ class JointAssaultBoostTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - UUID permanentId = targetPointer.getFirst(game, source); + UUID permanentId = getTargetPointer().getFirst(game, source); Permanent target = game.getPermanent(permanentId); if (target != null) { diff --git a/Mage.Sets/src/mage/cards/j/JoragaTreespeaker.java b/Mage.Sets/src/mage/cards/j/JoragaTreespeaker.java index b5f8b4d9d8d..47e92d67f2d 100644 --- a/Mage.Sets/src/mage/cards/j/JoragaTreespeaker.java +++ b/Mage.Sets/src/mage/cards/j/JoragaTreespeaker.java @@ -1,5 +1,3 @@ - - package mage.cards.j; import java.util.UUID; @@ -21,7 +19,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** @@ -30,7 +28,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class JoragaTreespeaker extends LevelerCard { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Elves"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Elves"); static { filter.add(SubType.ELF.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/j/JudgeOfCurrents.java b/Mage.Sets/src/mage/cards/j/JudgeOfCurrents.java index 3cde57a01e0..18fec7867f0 100644 --- a/Mage.Sets/src/mage/cards/j/JudgeOfCurrents.java +++ b/Mage.Sets/src/mage/cards/j/JudgeOfCurrents.java @@ -1,4 +1,3 @@ - package mage.cards.j; import java.util.UUID; @@ -9,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -17,6 +16,8 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class JudgeOfCurrents extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.MERFOLK, "a Merfolk you control"); + public JudgeOfCurrents(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}"); this.subtype.add(SubType.MERFOLK); @@ -25,7 +26,7 @@ public final class JudgeOfCurrents extends CardImpl { this.toughness = new MageInt(1); // Whenever a Merfolk you control becomes tapped, you may gain 1 life. - this.addAbility(new BecomesTappedTriggeredAbility(new GainLifeEffect(1), true, new FilterControlledCreaturePermanent(SubType.MERFOLK, "a Merfolk you control"))); + this.addAbility(new BecomesTappedTriggeredAbility(new GainLifeEffect(1), true, filter)); } private JudgeOfCurrents(final JudgeOfCurrents card) { diff --git a/Mage.Sets/src/mage/cards/j/JudithCarnageConnoisseur.java b/Mage.Sets/src/mage/cards/j/JudithCarnageConnoisseur.java new file mode 100644 index 00000000000..a909d5d45ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JudithCarnageConnoisseur.java @@ -0,0 +1,95 @@ +package mage.cards.j; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.constants.SubLayer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.ImpToken; +import mage.game.stack.Spell; + +/** + * + * @author DominionSpy + */ +public final class JudithCarnageConnoisseur extends CardImpl { + + public JudithCarnageConnoisseur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SHAMAN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Whenever you cast an instant or sorcery spell, choose one -- + // * That spell gains deathtouch and lifelink. + Ability ability = new SpellCastControllerTriggeredAbility( + new JudithCarnageConnoisseurEffect(), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, + false, SetTargetPointer.SPELL); + + // * Create a 2/2 red Imp creature token with "When this creature dies, it deals 2 damage to each opponent." + Mode mode = new Mode(new CreateTokenEffect(new ImpToken())); + ability.addMode(mode); + this.addAbility(ability); + } + + private JudithCarnageConnoisseur(final JudithCarnageConnoisseur card) { + super(card); + } + + @Override + public JudithCarnageConnoisseur copy() { + return new JudithCarnageConnoisseur(this); + } +} + +class JudithCarnageConnoisseurEffect extends ContinuousEffectImpl { + + JudithCarnageConnoisseurEffect() { + super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "That spell gains deathtouch and lifelink"; + } + + private JudithCarnageConnoisseurEffect(final JudithCarnageConnoisseurEffect effect) { + super(effect); + } + + @Override + public JudithCarnageConnoisseurEffect copy() { + return new JudithCarnageConnoisseurEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + discard(); + return false; + } + + Card card = spell.getCard(); + game.getState().addOtherAbility(card, DeathtouchAbility.getInstance()); + game.getState().addOtherAbility(card, LifelinkAbility.getInstance()); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/j/Junktown.java b/Mage.Sets/src/mage/cards/j/Junktown.java new file mode 100644 index 00000000000..5da17552e3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/Junktown.java @@ -0,0 +1,45 @@ +package mage.cards.j; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.JunkToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Junktown extends CardImpl { + + public Junktown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {4}{R}, {T}, Sacrifice Junktown: Create three Junk tokens. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new JunkToken(), 3), new ManaCostsImpl<>("{4}{R}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private Junktown(final Junktown card) { + super(card); + } + + @Override + public Junktown copy() { + return new Junktown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/Justice.java b/Mage.Sets/src/mage/cards/j/Justice.java index 6c9b0be27ec..8ef95622285 100644 --- a/Mage.Sets/src/mage/cards/j/Justice.java +++ b/Mage.Sets/src/mage/cards/j/Justice.java @@ -108,7 +108,7 @@ class JusticeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Integer damageAmount = (Integer) this.getValue("damageAmount"); - UUID targetId = this.targetPointer.getFirst(game, source); + UUID targetId = this.getTargetPointer().getFirst(game, source); if (damageAmount != null && targetId != null) { Player player = game.getPlayer(targetId); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/j/Juxtapose.java b/Mage.Sets/src/mage/cards/j/Juxtapose.java index 1167d2e339d..9bcc03e6cc6 100644 --- a/Mage.Sets/src/mage/cards/j/Juxtapose.java +++ b/Mage.Sets/src/mage/cards/j/Juxtapose.java @@ -75,8 +75,9 @@ class JuxtaposeEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); Player you = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (you != null && targetPlayer != null) { Permanent permanent1 = chooseOnePermanentsWithTheHighestCMC(you, filter, source, game); diff --git a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java index 92f7f0b4a95..80a07cd088c 100644 --- a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java +++ b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java @@ -88,7 +88,7 @@ class KaitoDancingShadowEffect extends OneShotEffect { if (watcher == null) { return false; } - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (controller == null || damagedPlayer == null) { return false; diff --git a/Mage.Sets/src/mage/cards/k/KalitasTraitorOfGhet.java b/Mage.Sets/src/mage/cards/k/KalitasTraitorOfGhet.java index 82e6575b465..f1aeda522b9 100644 --- a/Mage.Sets/src/mage/cards/k/KalitasTraitorOfGhet.java +++ b/Mage.Sets/src/mage/cards/k/KalitasTraitorOfGhet.java @@ -1,7 +1,5 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -16,7 +14,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; @@ -26,14 +24,15 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.ZombieToken; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** * @author fireshoes */ public final class KalitasTraitorOfGhet extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Vampire or Zombie"); + private static final FilterPermanent filter = new FilterPermanent("another Vampire or Zombie"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/k/KamiOfTheHonoredDead.java b/Mage.Sets/src/mage/cards/k/KamiOfTheHonoredDead.java index dd7c7bf8fb7..3ae93797c62 100644 --- a/Mage.Sets/src/mage/cards/k/KamiOfTheHonoredDead.java +++ b/Mage.Sets/src/mage/cards/k/KamiOfTheHonoredDead.java @@ -1,23 +1,17 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.SoulshiftAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.players.Player; + +import java.util.UUID; /** * @@ -36,7 +30,8 @@ public final class KamiOfTheHonoredDead extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Kami of the Honored Dead is dealt damage, you gain that much life. - this.addAbility(new KamiOfTheHonoredDeadTriggeredAbility()); + this.addAbility(new DealtDamageToSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH), false)); + // Soulshift 6 (When this creature dies, you may return target Spirit card with converted mana cost 6 or less from your graveyard to your hand.) this.addAbility(new SoulshiftAbility(6)); } @@ -50,63 +45,3 @@ public final class KamiOfTheHonoredDead extends CardImpl { return new KamiOfTheHonoredDead(this); } } - -class KamiOfTheHonoredDeadTriggeredAbility extends TriggeredAbilityImpl { - - public KamiOfTheHonoredDeadTriggeredAbility() { - super(Zone.BATTLEFIELD, new KamiOfTheHonoredDeadGainLifeEffect()); - setTriggerPhrase("Whenever {this} is dealt damage, "); - } - - private KamiOfTheHonoredDeadTriggeredAbility(final KamiOfTheHonoredDeadTriggeredAbility effect) { - super(effect); - } - - @Override - public KamiOfTheHonoredDeadTriggeredAbility copy() { - return new KamiOfTheHonoredDeadTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId)) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } -} - - -class KamiOfTheHonoredDeadGainLifeEffect extends OneShotEffect { - - public KamiOfTheHonoredDeadGainLifeEffect() { - super(Outcome.GainLife); - staticText = "you gain that much life"; - } - - private KamiOfTheHonoredDeadGainLifeEffect(final KamiOfTheHonoredDeadGainLifeEffect effect) { - super(effect); - } - - @Override - public KamiOfTheHonoredDeadGainLifeEffect copy() { - return new KamiOfTheHonoredDeadGainLifeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife((Integer) this.getValue("damageAmount"), game, source); - } - return true; - } - - -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KarganDragonrider.java b/Mage.Sets/src/mage/cards/k/KarganDragonrider.java index b62060dad09..80849c15163 100644 --- a/Mage.Sets/src/mage/cards/k/KarganDragonrider.java +++ b/Mage.Sets/src/mage/cards/k/KarganDragonrider.java @@ -13,7 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -21,7 +21,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class KarganDragonrider extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.DRAGON, "a Dragon"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DRAGON, "a Dragon"); public KarganDragonrider(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); diff --git a/Mage.Sets/src/mage/cards/k/KarlovWatchdog.java b/Mage.Sets/src/mage/cards/k/KarlovWatchdog.java new file mode 100644 index 00000000000..1166eef2bc7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KarlovWatchdog.java @@ -0,0 +1,83 @@ +package mage.cards.k; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author DominionSpy + */ +public final class KarlovWatchdog extends CardImpl { + + public KarlovWatchdog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.DOG); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Permanents your opponents control can't be turned face up during your turn. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new KarlovWatchdogEffect())); + + // Whenever you attack with three or more creatures, creatures you control get +1/+1 until end of turn. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + new BoostControlledEffect(1, 1, Duration.EndOfTurn), 3)); + } + + private KarlovWatchdog(final KarlovWatchdog card) { + super(card); + } + + @Override + public KarlovWatchdog copy() { + return new KarlovWatchdog(this); + } +} + +class KarlovWatchdogEffect extends ContinuousRuleModifyingEffectImpl { + + KarlovWatchdogEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Permanents your opponents control can't be turned face up during your turn"; + } + + private KarlovWatchdogEffect(final KarlovWatchdogEffect effect) { + super(effect); + } + + @Override + public KarlovWatchdogEffect copy() { + return new KarlovWatchdogEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TURN_FACE_UP; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + return permanent != null && game.isActivePlayer(source.getControllerId()) && + game.getOpponents(source.getControllerId()).contains(permanent.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/k/Karma.java b/Mage.Sets/src/mage/cards/k/Karma.java index 9e72015d5eb..87451c32779 100644 --- a/Mage.Sets/src/mage/cards/k/Karma.java +++ b/Mage.Sets/src/mage/cards/k/Karma.java @@ -60,9 +60,9 @@ class KarmaDamageTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - int damage = game.getBattlefield().getAllActivePermanents(filter, targetPointer.getFirst(game, source), game).size(); + int damage = game.getBattlefield().getAllActivePermanents(filter, getTargetPointer().getFirst(game, source), game).size(); player.damage(damage, source.getSourceId(), source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/k/KarnLiberated.java b/Mage.Sets/src/mage/cards/k/KarnLiberated.java index 38a03f45ba1..8e46cccba02 100644 --- a/Mage.Sets/src/mage/cards/k/KarnLiberated.java +++ b/Mage.Sets/src/mage/cards/k/KarnLiberated.java @@ -231,7 +231,7 @@ class KarnPlayerExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (sourceObject == null) { return false; diff --git a/Mage.Sets/src/mage/cards/k/KatildaAndLier.java b/Mage.Sets/src/mage/cards/k/KatildaAndLier.java index 12fca40958f..c2395fa495e 100644 --- a/Mage.Sets/src/mage/cards/k/KatildaAndLier.java +++ b/Mage.Sets/src/mage/cards/k/KatildaAndLier.java @@ -75,7 +75,7 @@ class KatildaAndLierEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); diff --git a/Mage.Sets/src/mage/cards/k/KaustEyesOfTheGlade.java b/Mage.Sets/src/mage/cards/k/KaustEyesOfTheGlade.java new file mode 100644 index 00000000000..566fd357b30 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KaustEyesOfTheGlade.java @@ -0,0 +1,117 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TurnFaceUpTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.card.FaceDownPredicate; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KaustEyesOfTheGlade extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature you control that was turned face up this turn"); + private static final FilterPermanent filter2 + = new FilterControlledCreaturePermanent("face-down attacking creature you control"); + + static { + filter.add(KaustEyesOfTheGladePredicate.instance); + filter2.add(AttackingPredicate.instance); + filter2.add(FaceDownPredicate.instance); + } + + public KaustEyesOfTheGlade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R/W}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DRYAD); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever a creature you control that was turned face up this turn deals combat damage to a player, draw a card. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter, + false, SetTargetPointer.NONE, true + ), new KaustEyesOfTheGladeWatcher()); + + // {T}: Turn target face-down attacking creature you control face up. + Ability ability = new SimpleActivatedAbility(new TurnFaceUpTargetEffect(), new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter2)); + this.addAbility(ability); + } + + private KaustEyesOfTheGlade(final KaustEyesOfTheGlade card) { + super(card); + } + + @Override + public KaustEyesOfTheGlade copy() { + return new KaustEyesOfTheGlade(this); + } +} + +enum KaustEyesOfTheGladePredicate implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return KaustEyesOfTheGladeWatcher.checkPermanent(input, game); + } +} + +class KaustEyesOfTheGladeWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + KaustEyesOfTheGladeWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.TURNED_FACE_UP) { + return; + } + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + set.add(new MageObjectReference(permanent, game)); + } + } + + @Override + public void reset() { + set.clear(); + super.reset(); + } + + static boolean checkPermanent(Permanent permanent, Game game) { + return game + .getState() + .getWatcher(KaustEyesOfTheGladeWatcher.class) + .set + .contains(new MageObjectReference(permanent, game)); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java new file mode 100644 index 00000000000..531853ca51a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java @@ -0,0 +1,282 @@ +package mage.cards.k; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.game.permanent.token.WhiteBlackSpiritToken; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetpointer.EachTargetPointer; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; +import mage.util.functions.CopyApplier; + +/** + * + * @author DominionSpy + */ +public final class KayaSpiritsJustice extends CardImpl { + + public KayaSpiritsJustice(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.KAYA); + this.setStartingLoyalty(3); + + // Whenever one or more creatures you control and/or creature cards in your graveyard are put into exile, you may choose a creature card from among them. + // Until end of turn, target token you control becomes a copy of it, except it has flying. + Ability ability = new KayaSpiritsJusticeTriggeredAbility(); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_TOKEN)); + this.addAbility(ability); + + // +2: Surveil 2, then exile a card from a graveyard. + ability = new LoyaltyAbility(new SurveilEffect(2, false), 2); + ability.addEffect(new KayaSpiritsJusticeExileEffect().concatBy(", then")); + this.addAbility(ability); + // +1: Create a 1/1 white and black Spirit creature token with flying. + ability = new LoyaltyAbility(new CreateTokenEffect(new WhiteBlackSpiritToken()), 1); + this.addAbility(ability); + // -2: Exile target creature you control. For each other player, exile up to one target creature that player controls. + ability = new LoyaltyAbility(new ExileTargetEffect() + .setText("exile target creature you control. For each other player, " + + "exile up to one target creature that player controls") + .setTargetPointer(new EachTargetPointer()), -2); + ability.setTargetAdjuster(KayaSpiritsJusticeAdjuster.instance); + this.addAbility(ability); + } + + private KayaSpiritsJustice(final KayaSpiritsJustice card) { + super(card); + } + + @Override + public KayaSpiritsJustice copy() { + return new KayaSpiritsJustice(this); + } +} + +class KayaSpiritsJusticeTriggeredAbility extends TriggeredAbilityImpl { + + KayaSpiritsJusticeTriggeredAbility() { + super(Zone.BATTLEFIELD, new KayaSpiritsJusticeCopyEffect(), false); + setTriggerPhrase("Whenever one or more creatures you control and/or creature cards in your graveyard are put into exile, " + + "you may choose a creature card from among them. Until end of turn, target token you control becomes a copy of it, " + + "except it has flying."); + } + + private KayaSpiritsJusticeTriggeredAbility(final KayaSpiritsJusticeTriggeredAbility ability) { + super(ability); + } + + @Override + public KayaSpiritsJusticeTriggeredAbility copy() { + return new KayaSpiritsJusticeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + if (zEvent == null) { + return false; + } + + Set battlefieldCards = zEvent.getEvents() + .stream() + .filter(e -> e.getFromZone() == Zone.BATTLEFIELD) + .filter(e -> e.getToZone() == Zone.EXILED) + .map(ZoneChangeEvent::getTargetId) + .filter(Objects::nonNull) + .map(game::getCard) + .filter(Objects::nonNull) + .filter(card -> { + Permanent permanent = game.getPermanentOrLKIBattlefield(card.getId()); + return StaticFilters.FILTER_PERMANENT_CREATURE + .match(permanent, getControllerId(), this, game); + }) + .collect(Collectors.toSet()); + + Set graveyardCards = zEvent.getEvents() + .stream() + .filter(e -> e.getFromZone() == Zone.GRAVEYARD) + .filter(e -> e.getToZone() == Zone.EXILED) + .map(ZoneChangeEvent::getTargetId) + .filter(Objects::nonNull) + .map(game::getCard) + .filter(Objects::nonNull) + .filter(card -> StaticFilters.FILTER_CARD_CREATURE + .match(card, getControllerId(), this, game)) + .collect(Collectors.toSet()); + + Set cards = new HashSet<>(battlefieldCards); + cards.addAll(graveyardCards); + if (cards.isEmpty()) { + return false; + } + + getEffects().setTargetPointer(new FixedTargets(new CardsImpl(cards), game)); + return true; + } +} + +class KayaSpiritsJusticeCopyEffect extends OneShotEffect { + + KayaSpiritsJusticeCopyEffect() { + super(Outcome.Copy); + } + + private KayaSpiritsJusticeCopyEffect(final KayaSpiritsJusticeCopyEffect effect) { + super(effect); + } + + @Override + public KayaSpiritsJusticeCopyEffect copy() { + return new KayaSpiritsJusticeCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent copyToPermanent = game.getPermanent(source.getFirstTarget()); + Cards exiledCards = new CardsImpl(getTargetPointer().getTargets(game, source)); + if (controller == null || copyToPermanent == null || exiledCards.isEmpty()) { + return false; + } + + TargetCard target = new TargetCardInExile(0, 1, StaticFilters.FILTER_CARD_CREATURE, null); + if (!controller.chooseTarget(outcome, exiledCards, target, source, game)) { + return false; + } + + Card copyFromCard = game.getCard(target.getFirstTarget()); + if (copyFromCard == null) { + return false; + } + + Permanent newBlueprint = new PermanentCard(copyFromCard, source.getControllerId(), game); + newBlueprint.assignNewId(); + CopyApplier applier = new KayaSpiritsJusticeCopyApplier(); + applier.apply(game, newBlueprint, source, copyToPermanent.getId()); + CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBlueprint, copyToPermanent.getId()); + copyEffect.newId(); + copyEffect.setApplier(applier); + Ability newAbility = source.copy(); + copyEffect.init(newAbility, game); + game.addEffect(copyEffect, source); + + return true; + } +} + +class KayaSpiritsJusticeCopyApplier extends CopyApplier { + + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.getAbilities().add(FlyingAbility.getInstance()); + return true; + } +} + +class KayaSpiritsJusticeExileEffect extends OneShotEffect { + + KayaSpiritsJusticeExileEffect() { + super(Outcome.Exile); + staticText = "exile a card from a graveyard"; + } + + private KayaSpiritsJusticeExileEffect(final KayaSpiritsJusticeExileEffect effect) { + super(effect); + } + + @Override + public KayaSpiritsJusticeExileEffect copy() { + return new KayaSpiritsJusticeExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + TargetCardInGraveyard target = new TargetCardInGraveyard(); + target.withNotTarget(true); + controller.choose(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + MageObject sourceObject = source.getSourceObject(game); + String exileName = sourceObject == null ? null : sourceObject.getIdName(); + return controller.moveCardsToExile(card, source, game, true, exileId, exileName); + } + } + return false; + } +} + +enum KayaSpiritsJusticeAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { + return; + } + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE)); + + for (UUID playerId : game.getState().getPlayersInRange(ability.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null || player == controller) { + continue; + } + FilterPermanent filter = new FilterCreaturePermanent("creature that player controls"); + filter.add(new ControllerIdPredicate(playerId)); + ability.addTarget(new TargetPermanent(0, 1, filter) + .withChooseHint("from " + player.getLogName())); + } + } +} diff --git a/Mage.Sets/src/mage/cards/k/KazuulTyrantOfTheCliffs.java b/Mage.Sets/src/mage/cards/k/KazuulTyrantOfTheCliffs.java index fde22fa783e..939a53815a5 100644 --- a/Mage.Sets/src/mage/cards/k/KazuulTyrantOfTheCliffs.java +++ b/Mage.Sets/src/mage/cards/k/KazuulTyrantOfTheCliffs.java @@ -99,7 +99,7 @@ class KazuulTyrantOfTheCliffsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player payee = game.getPlayer(targetPointer.getFirst(game, source)); + Player payee = game.getPlayer(getTargetPointer().getFirst(game, source)); if (payee == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/k/KellanInquisitiveProdigy.java b/Mage.Sets/src/mage/cards/k/KellanInquisitiveProdigy.java new file mode 100644 index 00000000000..273c096e200 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KellanInquisitiveProdigy.java @@ -0,0 +1,102 @@ +package mage.cards.k; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.AdventureCard; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +/** + * + * @author DominionSpy + */ +public final class KellanInquisitiveProdigy extends AdventureCard { + + public KellanInquisitiveProdigy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{2}{G}{U}", "Tail the Suspect", "{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.FAERIE); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Kellan, Inquisitive Prodigy attacks, destroy up to one target artifact. If you controlled that permanent, draw a card. + Ability ability = new AttacksTriggeredAbility(new KellanInquisitiveProdigyEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_ARTIFACT)); + this.addAbility(ability); + + // Tail the Suspect + // Investigate. You may play an additional land this turn. + this.getSpellCard().getSpellAbility().addEffect(new InvestigateEffect(false)); + this.getSpellCard().getSpellAbility().addEffect(new PlayAdditionalLandsControllerEffect(1, Duration.EndOfTurn)); + + this.finalizeAdventure(); + } + + private KellanInquisitiveProdigy(final KellanInquisitiveProdigy card) { + super(card); + } + + @Override + public KellanInquisitiveProdigy copy() { + return new KellanInquisitiveProdigy(this); + } +} + +class KellanInquisitiveProdigyEffect extends OneShotEffect { + + KellanInquisitiveProdigyEffect() { + super(Outcome.Benefit); + staticText = "destroy up to one target artifact. " + + "If you controlled that permanent, draw a card"; + } + + private KellanInquisitiveProdigyEffect(final KellanInquisitiveProdigyEffect effect) { + super(effect); + } + + @Override + public KellanInquisitiveProdigyEffect copy() { + return new KellanInquisitiveProdigyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (controller == null || permanent == null) { + return false; + } + + boolean isMine = permanent.isControlledBy(source.getControllerId()); + permanent.destroy(source, game, false); + if (isMine) { + controller.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KelloggDangerousMind.java b/Mage.Sets/src/mage/cards/k/KelloggDangerousMind.java new file mode 100644 index 00000000000..25241022b1c --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KelloggDangerousMind.java @@ -0,0 +1,65 @@ +package mage.cards.k; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class KelloggDangerousMind extends CardImpl { + + private static final FilterControlledPermanent filterTreasures = new FilterControlledPermanent(SubType.TREASURE, "Treasures"); + + public KelloggDangerousMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Kellogg, Dangerous Mind attacks, create a Treasure token. + this.addAbility(new AttacksTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + + // Sacrifice five Treasures: Gain control of target creature for as long as you control Kellogg. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new GainControlTargetEffect(Duration.WhileControlled), + new SacrificeTargetCost(5, filterTreasures)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private KelloggDangerousMind(final KelloggDangerousMind card) { + super(card); + } + + @Override + public KelloggDangerousMind copy() { + return new KelloggDangerousMind(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KelpieGuide.java b/Mage.Sets/src/mage/cards/k/KelpieGuide.java index cc5d1de32d2..8637b8b6589 100644 --- a/Mage.Sets/src/mage/cards/k/KelpieGuide.java +++ b/Mage.Sets/src/mage/cards/k/KelpieGuide.java @@ -17,6 +17,7 @@ import mage.constants.ComparisonType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledLandPermanent; import mage.target.TargetPermanent; import java.util.UUID; @@ -26,8 +27,9 @@ import java.util.UUID; */ public final class KelpieGuide extends CardImpl { + private static final FilterControlledLandPermanent filter = new FilterControlledLandPermanent("you control eight or more lands"); private static final Condition condition - = new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, ComparisonType.MORE_THAN, 7); + = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.MORE_THAN, 7); public KelpieGuide(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); diff --git a/Mage.Sets/src/mage/cards/k/KheruLichLord.java b/Mage.Sets/src/mage/cards/k/KheruLichLord.java index 0b2814030c9..7f16c65aa70 100644 --- a/Mage.Sets/src/mage/cards/k/KheruLichLord.java +++ b/Mage.Sets/src/mage/cards/k/KheruLichLord.java @@ -81,26 +81,26 @@ class KheruLichLordEffect extends OneShotEffect { controller.moveCards(card, Zone.BATTLEFIELD, source, game); Permanent permanent = game.getPermanent(card.getId()); if (permanent != null) { - FixedTarget fixedTarget = new FixedTarget(permanent, game); + FixedTarget blueprintTarget = new FixedTarget(permanent, game); ContinuousEffect effect = new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); effect = new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); ExileTargetEffect exileEffect = new ExileTargetEffect(); - exileEffect.setTargetPointer(fixedTarget); + exileEffect.setTargetPointer(blueprintTarget.copy()); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect); game.addDelayedTriggeredAbility(delayedAbility, source); ReplacementEffect replacementEffect = new LeaveBattlefieldExileTargetReplacementEffect("that card"); - replacementEffect.setTargetPointer(fixedTarget); + replacementEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(replacementEffect, source); } } diff --git a/Mage.Sets/src/mage/cards/k/KheruMindEater.java b/Mage.Sets/src/mage/cards/k/KheruMindEater.java index ad34182ad1d..78e16f7c8ad 100644 --- a/Mage.Sets/src/mage/cards/k/KheruMindEater.java +++ b/Mage.Sets/src/mage/cards/k/KheruMindEater.java @@ -74,7 +74,7 @@ class KheruMindEaterExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null && !player.getHand().isEmpty()) { Target target = new TargetCardInHand(1, new FilterCard()); target.chooseTarget(Outcome.Exile, player.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java b/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java index 98c6be1f7b6..189a5f55400 100644 --- a/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java +++ b/Mage.Sets/src/mage/cards/k/KheruSpellsnatcher.java @@ -71,9 +71,9 @@ class KheruSpellsnatcherEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { MageObject sourceObject = source.getSourceObject(game); - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackObject != null && sourceObject != null - && game.getStack().counter(targetPointer.getFirst(game, source), source, game, PutCards.EXILED)) { + && game.getStack().counter(getTargetPointer().getFirst(game, source), source, game, PutCards.EXILED)) { if (!stackObject.isCopy()) { MageObject card = game.getObject(stackObject.getSourceId()); if (card instanceof Card) { diff --git a/Mage.Sets/src/mage/cards/k/KikuNightsFlower.java b/Mage.Sets/src/mage/cards/k/KikuNightsFlower.java index 748b2c0d081..22d3e422582 100644 --- a/Mage.Sets/src/mage/cards/k/KikuNightsFlower.java +++ b/Mage.Sets/src/mage/cards/k/KikuNightsFlower.java @@ -75,7 +75,7 @@ class KikuNightsFlowerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(permanent.getPower().getValue(), permanent.getId(), source, game, false, true); return true; diff --git a/Mage.Sets/src/mage/cards/k/KillSuitCultist.java b/Mage.Sets/src/mage/cards/k/KillSuitCultist.java index ce0f9b0633d..384789b555a 100644 --- a/Mage.Sets/src/mage/cards/k/KillSuitCultist.java +++ b/Mage.Sets/src/mage/cards/k/KillSuitCultist.java @@ -77,12 +77,12 @@ class KillSuitCultistEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(targetPointer.getFirst(game, source)); + return event.getTargetId().equals(getTargetPointer().getFirst(game, source)); } @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if(permanent != null) { permanent.destroy(source, game, false); return true; diff --git a/Mage.Sets/src/mage/cards/k/KillerInstinct.java b/Mage.Sets/src/mage/cards/k/KillerInstinct.java index 97d19d07766..5b349790c49 100644 --- a/Mage.Sets/src/mage/cards/k/KillerInstinct.java +++ b/Mage.Sets/src/mage/cards/k/KillerInstinct.java @@ -85,12 +85,12 @@ class KillerInstinctEffect extends OneShotEffect { if (card.isCreature(game) && player.moveCards(card, Zone.BATTLEFIELD, source, game)) { Permanent permanent = game.getPermanent(card.getId()); if (permanent != null) { - FixedTarget ft = new FixedTarget(permanent, game); + FixedTarget blueprintTarget = new FixedTarget(permanent, game); ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - effect.setTargetPointer(ft); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); Effect sacrificeEffect = new SacrificeTargetEffect("Sacrifice it at the beginning of the next end step", source.getControllerId()); - sacrificeEffect.setTargetPointer(ft); + sacrificeEffect.setTargetPointer(blueprintTarget.copy()); game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source); } return true; diff --git a/Mage.Sets/src/mage/cards/k/KingOfTheOathbreakers.java b/Mage.Sets/src/mage/cards/k/KingOfTheOathbreakers.java index 2d675298ab6..66bc1371502 100644 --- a/Mage.Sets/src/mage/cards/k/KingOfTheOathbreakers.java +++ b/Mage.Sets/src/mage/cards/k/KingOfTheOathbreakers.java @@ -14,7 +14,7 @@ import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.FilterPermanentThisOrAnother; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SpiritWhiteToken; import java.util.UUID; @@ -26,9 +26,7 @@ import java.util.UUID; public final class KingOfTheOathbreakers extends CardImpl { private static final FilterPermanent filter = - new FilterPermanentThisOrAnother( - new FilterControlledCreaturePermanent(SubType.SPIRIT), - true); + new FilterPermanentThisOrAnother(new FilterControlledPermanent(SubType.SPIRIT), true); public KingOfTheOathbreakers(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}"); diff --git a/Mage.Sets/src/mage/cards/k/KirinTouchedOrochi.java b/Mage.Sets/src/mage/cards/k/KirinTouchedOrochi.java index bf852718875..09d03710924 100644 --- a/Mage.Sets/src/mage/cards/k/KirinTouchedOrochi.java +++ b/Mage.Sets/src/mage/cards/k/KirinTouchedOrochi.java @@ -17,6 +17,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterNoncreatureCard; import mage.game.Game; @@ -31,7 +32,6 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class KirinTouchedOrochi extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("creature card from a graveyard"); private static final FilterNoncreatureCard filter2 = new FilterNoncreatureCard("noncreature card from a graveyard"); public KirinTouchedOrochi(UUID ownerId, CardSetInfo setInfo) { @@ -47,7 +47,7 @@ public final class KirinTouchedOrochi extends CardImpl { // Whenever Kirin-Touched Orochi attacks, choose one — // • Exile target creature card from a graveyard. When you do, create a 1/1 colorless Spirit creature token. Ability ability = new AttacksTriggeredAbility(new KirinTouchedOrochiTokenEffect()); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // • Exile target noncreature card from a graveyard. When you do, put a +1/+1 counter on target creature you control. Mode mode = new Mode(new KirinTouchedOrochiCounterEffect()); diff --git a/Mage.Sets/src/mage/cards/k/KithkinZealot.java b/Mage.Sets/src/mage/cards/k/KithkinZealot.java index 1eddeb02344..b367fafa205 100644 --- a/Mage.Sets/src/mage/cards/k/KithkinZealot.java +++ b/Mage.Sets/src/mage/cards/k/KithkinZealot.java @@ -75,7 +75,7 @@ class KithkinZealotEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (you!= null && opponent != null) { int amount = game.getBattlefield().countAll(filter, opponent.getId(), game); diff --git a/Mage.Sets/src/mage/cards/k/KnacksawClique.java b/Mage.Sets/src/mage/cards/k/KnacksawClique.java index a5d1e7cfef9..23c97ddb412 100644 --- a/Mage.Sets/src/mage/cards/k/KnacksawClique.java +++ b/Mage.Sets/src/mage/cards/k/KnacksawClique.java @@ -79,7 +79,7 @@ class KnacksawCliqueEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (sourceObject != null && opponent != null) { if (opponent.getLibrary().hasCards()) { diff --git a/Mage.Sets/src/mage/cards/k/KnightCaptainOfEos.java b/Mage.Sets/src/mage/cards/k/KnightCaptainOfEos.java index c47438420c9..dc8c6dae5fa 100644 --- a/Mage.Sets/src/mage/cards/k/KnightCaptainOfEos.java +++ b/Mage.Sets/src/mage/cards/k/KnightCaptainOfEos.java @@ -1,7 +1,5 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -15,9 +13,10 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanent; import mage.game.permanent.token.SoldierToken; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** * @@ -25,11 +24,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class KnightCaptainOfEos extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Soldier"); - - static { - filter.add(SubType.SOLDIER.getPredicate()); - } + private static final FilterPermanent filter = new FilterPermanent(SubType.SOLDIER, "a Soldier"); public KnightCaptainOfEos(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}"); @@ -39,7 +34,10 @@ public final class KnightCaptainOfEos extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); + // When Knight-Captain of Eos enters the battlefield, create two 1/1 white Soldier creature tokens. this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new SoldierToken(), 2), false)); + + // {W}, Sacrifice a Soldier: Prevent all combat damage that would be dealt this turn. SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true), new ManaCostsImpl<>("{W}")); ability.addCost(new SacrificeTargetCost(filter)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/k/KnollspineDragon.java b/Mage.Sets/src/mage/cards/k/KnollspineDragon.java index 58358dad250..4c573bdcd54 100644 --- a/Mage.Sets/src/mage/cards/k/KnollspineDragon.java +++ b/Mage.Sets/src/mage/cards/k/KnollspineDragon.java @@ -64,7 +64,7 @@ class KnollspineDragonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetOpponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null) { new DiscardHandControllerEffect().apply(game, source); if (targetOpponent != null) { diff --git a/Mage.Sets/src/mage/cards/k/KnowledgeIsPower.java b/Mage.Sets/src/mage/cards/k/KnowledgeIsPower.java new file mode 100644 index 00000000000..441a8c28eeb --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KnowledgeIsPower.java @@ -0,0 +1,38 @@ +package mage.cards.k; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class KnowledgeIsPower extends CardImpl { + + public KnowledgeIsPower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{U}"); + + // Creatures you control get +X/+X, where X is the number of cards you've drawn this turn. + this.addAbility(new SimpleStaticAbility(new BoostAllEffect( + CardsDrawnThisTurnDynamicValue.instance, CardsDrawnThisTurnDynamicValue.instance, + Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE, false + ).setText("Creatures you control get +X/+X, where X is the number of cards you've drawn this turn") + ).addHint(CardsDrawnThisTurnDynamicValue.getHint())); + } + + private KnowledgeIsPower(final KnowledgeIsPower card) { + super(card); + } + + @Override + public KnowledgeIsPower copy() { + return new KnowledgeIsPower(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KnowledgePool.java b/Mage.Sets/src/mage/cards/k/KnowledgePool.java index ae63b78f594..356eeb493c7 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgePool.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgePool.java @@ -150,7 +150,7 @@ class KnowledgePoolExileAndPlayEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/k/KnowledgeVault.java b/Mage.Sets/src/mage/cards/k/KnowledgeVault.java index c5bf3c62143..e24f0d58ed8 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgeVault.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgeVault.java @@ -1,26 +1,23 @@ - package mage.cards.k; import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.ReturnFromExileForSourceEffect; import mage.abilities.effects.common.discard.DiscardHandControllerEffect; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.util.CardUtil; /** * @@ -32,13 +29,20 @@ public final class KnowledgeVault extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // {2}, {T}: Exile the top card of your library face down. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new KnowledgeVaultExileEffect(), new GenericManaCost(2))); + Ability ability = new SimpleActivatedAbility( + Zone.BATTLEFIELD, + new ExileCardsFromTopOfLibraryControllerEffect(1, true, true), + new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); // {0}: Sacrifice Knowledge Vault. If you do, discard your hand, then put all cards exiled with Knowledge Vault into their owner’s hand. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new KnowledgeVaultReturnEffect(), new GenericManaCost(0))); // When Knowledge Vault leaves the battlefield, put all cards exiled with Knowledge Vault into their owner’s graveyard. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.GRAVEYARD), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility( + new ReturnFromExileForSourceEffect(Zone.GRAVEYARD).withText(true, false, true), + false)); } private KnowledgeVault(final KnowledgeVault card) { @@ -51,45 +55,11 @@ public final class KnowledgeVault extends CardImpl { } } -class KnowledgeVaultExileEffect extends OneShotEffect { - - KnowledgeVaultExileEffect() { - super(Outcome.Exile); - this.staticText = "exile the top card of your library face down"; - } - - private KnowledgeVaultExileEffect(final KnowledgeVaultExileEffect effect) { - super(effect); - } - - @Override - public KnowledgeVaultExileEffect copy() { - return new KnowledgeVaultExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName()); - card.setFaceDown(true, game); - return true; - } - } - return false; - } -} - class KnowledgeVaultReturnEffect extends OneShotEffect { KnowledgeVaultReturnEffect() { super(Outcome.DrawCard); - this.staticText = "Sacrifice {this}. If you do, discard your hand, then put all cards exiled with {this} into their owners' hands"; + this.staticText = "Sacrifice {this}. If you do, discard your hand, then put all cards exiled with {this} into their owner's hand"; } private KnowledgeVaultReturnEffect(final KnowledgeVaultReturnEffect effect) { @@ -108,10 +78,7 @@ class KnowledgeVaultReturnEffect extends OneShotEffect { if (sourcePermanent != null && controller != null) { if (sourcePermanent.sacrifice(source, game)) { new DiscardHandControllerEffect().apply(game, source); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); - if (exileZone != null) { - controller.moveCards(exileZone, Zone.HAND, source, game); - } + new ReturnFromExileForSourceEffect(Zone.HAND).apply(game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/k/KodamasReach.java b/Mage.Sets/src/mage/cards/k/KodamasReach.java index 45d57fd0d8b..c7544159381 100644 --- a/Mage.Sets/src/mage/cards/k/KodamasReach.java +++ b/Mage.Sets/src/mage/cards/k/KodamasReach.java @@ -1,21 +1,12 @@ package mage.cards.k; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; import mage.cards.*; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; -import java.util.Set; import java.util.UUID; /** @@ -27,8 +18,9 @@ public final class KodamasReach extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); this.subtype.add(SubType.ARCANE); - // Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new KodamasReachEffect()); + // Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect( + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS))); } private KodamasReach(final KodamasReach card) { @@ -40,70 +32,3 @@ public final class KodamasReach extends CardImpl { return new KodamasReach(this); } } - -class KodamasReachEffect extends OneShotEffect { - - protected static final FilterCard filter = new FilterCard("card to put on the battlefield tapped"); - - public KodamasReachEffect() { - super(Outcome.PutLandInPlay); - staticText = "search your library for up to two basic land cards, reveal those cards, " + - "put one onto the battlefield tapped and the other into your hand, then shuffle"; - } - - private KodamasReachEffect(final KodamasReachEffect effect) { - super(effect); - } - - @Override - public KodamasReachEffect copy() { - return new KodamasReachEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); - if (controller == null || sourceObject == null) { - return false; - } - TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, source, game)) { - if (!target.getTargets().isEmpty()) { - Cards revealed = new CardsImpl(); - for (UUID cardId : target.getTargets()) { - Card card = controller.getLibrary().getCard(cardId, game); - revealed.add(card); - } - controller.revealCards(sourceObject.getIdName(), revealed, game); - if (target.getTargets().size() == 2) { - TargetCard target2 = new TargetCard(Zone.LIBRARY, filter); - controller.choose(Outcome.Benefit, revealed, target2, source, game); - Card card = revealed.get(target2.getFirstTarget(), game); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - revealed.remove(card); - } - Set cards = revealed.getCards(game); - card = cards.isEmpty() ? null : cards.iterator().next(); - if (card != null) { - controller.moveCards(card, Zone.HAND, source, game); - } - } else if (target.getTargets().size() == 1) { - Set cards = revealed.getCards(game); - Card card = cards.isEmpty() ? null : cards.iterator().next(); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - } - } - - } - controller.shuffleLibrary(source, game); - return true; - } - controller.shuffleLibrary(source, game); - return false; - - } - -} diff --git a/Mage.Sets/src/mage/cards/k/KondasBanner.java b/Mage.Sets/src/mage/cards/k/KondasBanner.java index 9f88d53e50d..c291ef7f33b 100644 --- a/Mage.Sets/src/mage/cards/k/KondasBanner.java +++ b/Mage.Sets/src/mage/cards/k/KondasBanner.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.cards.k; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/k/KravTheUnredeemed.java b/Mage.Sets/src/mage/cards/k/KravTheUnredeemed.java index 63fc817fd21..8f9fe851aea 100644 --- a/Mage.Sets/src/mage/cards/k/KravTheUnredeemed.java +++ b/Mage.Sets/src/mage/cards/k/KravTheUnredeemed.java @@ -44,7 +44,7 @@ public final class KravTheUnredeemed extends CardImpl { // {B}, Sacrifice X creatures: Target player draws X cards and gains X life. Put X +1/+1 counters on Krav, the Unredeemed. Ability ability = new SimpleActivatedAbility(new KravTheUnredeemedEffect(), new ManaCostsImpl<>("{B}")); ability.addTarget(new TargetPlayer()); - ability.addCost(new SacrificeXTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE)); + ability.addCost(new SacrificeXTargetCost(StaticFilters.FILTER_PERMANENT_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/k/KrenkosBuzzcrusher.java b/Mage.Sets/src/mage/cards/k/KrenkosBuzzcrusher.java new file mode 100644 index 00000000000..bbbb410d135 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KrenkosBuzzcrusher.java @@ -0,0 +1,132 @@ +package mage.cards.k; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author DominionSpy + */ +public final class KrenkosBuzzcrusher extends CardImpl { + + public KrenkosBuzzcrusher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.THOPTER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Krenko's Buzzcrusher enters the battlefield, for each player, destroy up to one nonbasic land that player controls. For each land destroyed this way, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new KrenkosBuzzcrusherEffect())); + } + + private KrenkosBuzzcrusher(final KrenkosBuzzcrusher card) { + super(card); + } + + @Override + public KrenkosBuzzcrusher copy() { + return new KrenkosBuzzcrusher(this); + } +} + +class KrenkosBuzzcrusherEffect extends OneShotEffect { + + KrenkosBuzzcrusherEffect() { + super(Outcome.DestroyPermanent); + staticText = "for each player, destroy up to one nonbasic land that player controls. " + + "For each land destroyed this way, its controller may search their library for a basic land card, " + + "put it onto the battlefield tapped, then shuffle"; + } + + private KrenkosBuzzcrusherEffect(final KrenkosBuzzcrusherEffect effect) { + super(effect); + } + + @Override + public KrenkosBuzzcrusherEffect copy() { + return new KrenkosBuzzcrusherEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + List chosenLands = new ArrayList<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + + FilterLandPermanent filter = new FilterLandPermanent("nonbasic land " + player.getName() + " controls"); + filter.add(new ControllerIdPredicate(playerId)); + filter.add(Predicates.not(SuperType.BASIC.getPredicate())); + TargetPermanent target = new TargetPermanent(0, 1, filter); + target.withNotTarget(true); + + controller.chooseTarget(outcome, target, source, game); + Permanent land = game.getPermanent(target.getFirstTarget()); + if (land != null) { + chosenLands.add(land); + } + } + + List destroyedLands = new ArrayList<>(); + for (Permanent land : chosenLands) { + if (land.destroy(source, game)) { + destroyedLands.add(land); + } + } + + for (Permanent land : destroyedLands) { + Player player = game.getPlayer(land.getControllerId()); + if (player == null) { + continue; + } + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND_A); + if (!player.chooseUse(Outcome.PutLandInPlay, "Search your library for " + target.getDescription() + "?", source, game)) { + continue; + } + if (player.searchLibrary(target, source, game)) { + player.moveCards(game.getCard(target.getFirstTarget()), Zone.BATTLEFIELD, + source, game, true, false, false, null); + player.shuffleLibrary(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KumanoMasterYamabushi.java b/Mage.Sets/src/mage/cards/k/KumanoMasterYamabushi.java index 2723db07c29..89910f0fe23 100644 --- a/Mage.Sets/src/mage/cards/k/KumanoMasterYamabushi.java +++ b/Mage.Sets/src/mage/cards/k/KumanoMasterYamabushi.java @@ -1,33 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ - package mage.cards.k; import mage.MageInt; diff --git a/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java b/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java new file mode 100644 index 00000000000..81d48b65a17 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java @@ -0,0 +1,163 @@ +package mage.cards.k; + +import java.util.UUID; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class KyloxsVoltstrider extends CardImpl { + + public KyloxsVoltstrider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}{R}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Collect evidence 6: Kylox's Voltstrider becomes an artifact creature until end of turn. + this.addAbility(new SimpleActivatedAbility( + new AddCardTypeSourceEffect(Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE) + .setText("{this} becomes an artifact creature until end of turn"), + new CollectEvidenceCost(6, true))); + + // Whenever Kylox's Voltstrider attacks, you may cast an instant or sorcery spell from among cards exiled with it. + // If that spell would be put into a graveyard, put it on the bottom of its owner's library instead. + this.addAbility(new AttacksTriggeredAbility(new KyloxsVoltstriderEffect(), true)); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private KyloxsVoltstrider(final KyloxsVoltstrider card) { + super(card); + } + + @Override + public KyloxsVoltstrider copy() { + return new KyloxsVoltstrider(this); + } +} + +class KyloxsVoltstriderEffect extends OneShotEffect { + + KyloxsVoltstriderEffect() { + super(Outcome.Benefit); + this.staticText = "you may cast an instant or sorcery spell from among cards exiled with it. " + + "If that spell would be put into a graveyard, put it on the bottom of its owner's library instead."; + } + + private KyloxsVoltstriderEffect(final KyloxsVoltstriderEffect effect) { + super(effect); + } + + @Override + public KyloxsVoltstriderEffect copy() { + return new KyloxsVoltstriderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), + game.getState().getZoneChangeCounter(source.getSourceId())); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (controller == null || exileZone == null) { + return false; + } + + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, exileId); + target.withNotTarget(true); + if (!target.canChoose(source.getControllerId(), source, game)) { + return true; + } + controller.chooseTarget(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + controller.cast(controller.chooseAbilityForCast(card, game, false), + game, false, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + + // If that spell would be put into a graveyard, put it on the bottom of its owner's library instead. + ContinuousEffect effect = new KyloxsVoltstriderReplacementEffect(); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } +} + +class KyloxsVoltstriderReplacementEffect extends ReplacementEffectImpl { + + KyloxsVoltstriderReplacementEffect() { + super(Duration.Custom, Outcome.Exile); + staticText = "If that spell would be put into a graveyard, put it on the bottom of its owner's library instead."; + } + + private KyloxsVoltstriderReplacementEffect(final KyloxsVoltstriderReplacementEffect effect) { + super(effect); + } + + @Override + public KyloxsVoltstriderReplacementEffect copy() { + return new KyloxsVoltstriderReplacementEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + return zEvent.getToZone() == Zone.GRAVEYARD && + event.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget()) && + ((FixedTarget) getTargetPointer()).getZoneChangeCounter() == + game.getState().getZoneChangeCounter(zEvent.getTargetId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null || controller == null) { + return false; + } + + controller.putCardsOnBottomOfLibrary(card, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KyrenArchive.java b/Mage.Sets/src/mage/cards/k/KyrenArchive.java index 808a1a43ad2..e51f3646873 100644 --- a/Mage.Sets/src/mage/cards/k/KyrenArchive.java +++ b/Mage.Sets/src/mage/cards/k/KyrenArchive.java @@ -2,25 +2,19 @@ package mage.cards.k; import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.DiscardHandCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; import mage.constants.Zone; -import mage.game.ExileZone; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; /** * @@ -32,12 +26,16 @@ public final class KyrenArchive extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // At the beginning of your upkeep, you may exile the top card of your library face down. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new KyrenArchiveExileEffect(), TargetController.YOU, true)); + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new ExileCardsFromTopOfLibraryControllerEffect(1, true, true), + TargetController.YOU, + true) + ); // {5}, Discard your hand, Sacrifice Kyren Archive: Put all cards exiled with Kyren Archive into their owner's hand. Ability ability = new SimpleActivatedAbility( Zone.BATTLEFIELD, - new KyrenArchiveReturnEffect(), + new ReturnFromExileForSourceEffect(Zone.HAND).withText(true, false, true), new GenericManaCost(5) ); ability.addCost(new DiscardHandCost()); @@ -54,67 +52,3 @@ public final class KyrenArchive extends CardImpl { return new KyrenArchive(this); } } - -class KyrenArchiveExileEffect extends OneShotEffect { - - KyrenArchiveExileEffect() { - super(Outcome.Exile); - this.staticText = "exile the top card of your library face down"; - } - - private KyrenArchiveExileEffect(final KyrenArchiveExileEffect effect) { - super(effect); - } - - @Override - public KyrenArchiveExileEffect copy() { - return new KyrenArchiveExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName()); - card.setFaceDown(true, game); - return true; - } - } - return false; - } -} - -class KyrenArchiveReturnEffect extends OneShotEffect { - - KyrenArchiveReturnEffect() { - super(Outcome.DrawCard); - this.staticText = "Put all cards exiled with {this} into their owners' hands"; - } - - private KyrenArchiveReturnEffect(final KyrenArchiveReturnEffect effect) { - super(effect); - } - - @Override - public KyrenArchiveReturnEffect copy() { - return new KyrenArchiveReturnEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); - if (exileZone != null) { - controller.moveCards(exileZone, Zone.HAND, source, game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LamplightPhoenix.java b/Mage.Sets/src/mage/cards/l/LamplightPhoenix.java new file mode 100644 index 00000000000..ebe61ac0f84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LamplightPhoenix.java @@ -0,0 +1,50 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlSourceEffect; +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 xenohedron + */ +public final class LamplightPhoenix extends CardImpl { + + public LamplightPhoenix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); + + this.subtype.add(SubType.PHOENIX); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Lamplight Phoenix dies, you may exile it and collect evidence 4. If you do, return Lamplight Phoenix to the battlefield tapped. + this.addAbility(new DiesSourceTriggeredAbility(new DoIfCostPaid( + new ReturnToBattlefieldUnderOwnerControlSourceEffect(true) + .setText("return {this} to the battlefield tapped"), + new CompositeCost(new ExileSourceCost(), new CollectEvidenceCost(4), + "exile it and collect evidence 4")) + )); + } + + private LamplightPhoenix(final LamplightPhoenix card) { + super(card); + } + + @Override + public LamplightPhoenix copy() { + return new LamplightPhoenix(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LandsEdge.java b/Mage.Sets/src/mage/cards/l/LandsEdge.java index 9e90164ebd9..df3a2cd96bd 100644 --- a/Mage.Sets/src/mage/cards/l/LandsEdge.java +++ b/Mage.Sets/src/mage/cards/l/LandsEdge.java @@ -69,7 +69,7 @@ class LandsEdgeEffect extends OneShotEffect { List cards = cost.getCards(); if (cards.size() == 1 && cards.get(0).isLand(game)) { Effect effect = new DamageTargetEffect(2); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } diff --git a/Mage.Sets/src/mage/cards/l/LaraCroftTombRaider.java b/Mage.Sets/src/mage/cards/l/LaraCroftTombRaider.java new file mode 100644 index 00000000000..4432801bb8f --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LaraCroftTombRaider.java @@ -0,0 +1,224 @@ +package mage.cards.l; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.RaidCondition; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.ReachAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.TreasureToken; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; +import mage.watchers.Watcher; +import mage.watchers.common.PlayerAttackedWatcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LaraCroftTombRaider extends CardImpl { + + private static final FilterCard filter = new FilterCard("legendary artifact card or legendary land card from a graveyard"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public LaraCroftTombRaider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Lara Croft attacks, exile up to one target legendary artifact card or legendary land card from a graveyard and put a discovery counter on it. You may play a card from exile with a discovery counter on it this turn. + Ability ability = new AttacksTriggeredAbility(new LaraCroftTombRaiderExileEffect()); + ability.addEffect(new LaraCroftTombRaiderCastEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1, filter)); + this.addAbility(ability.setIdentifier(MageIdentifier.LaraCroftTombRaiderWatcher), new LaraCroftTombRaiderWatcher()); + + // Raid -- At end of combat on your turn, if you attacked this turn, create a Treasure token. + this.addAbility(new LaraCroftTombRaiderTriggeredAbility()); + } + + private LaraCroftTombRaider(final LaraCroftTombRaider card) { + super(card); + } + + @Override + public LaraCroftTombRaider copy() { + return new LaraCroftTombRaider(this); + } +} + +class LaraCroftTombRaiderExileEffect extends OneShotEffect { + + LaraCroftTombRaiderExileEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target legendary artifact card " + + "or legendary land card from a graveyard and put a discovery counter on it"; + } + + private LaraCroftTombRaiderExileEffect(final LaraCroftTombRaiderExileEffect effect) { + super(effect); + } + + @Override + public LaraCroftTombRaiderExileEffect copy() { + return new LaraCroftTombRaiderExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + card.addCounters(CounterType.DISCOVERY.createInstance(), source, game); + return true; + } +} + +class LaraCroftTombRaiderCastEffect extends AsThoughEffectImpl { + + LaraCroftTombRaiderCastEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "you may play a card from exile with a discovery counter on it this turn"; + } + + private LaraCroftTombRaiderCastEffect(final LaraCroftTombRaiderCastEffect effect) { + super(effect); + } + + @Override + public LaraCroftTombRaiderCastEffect copy() { + return new LaraCroftTombRaiderCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + LaraCroftTombRaiderWatcher.incrementWatcher(source.getControllerId(), game); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + Card card = game.getCard(sourceId); + return card != null + && game.getState().getZone(sourceId) == Zone.EXILED + && card.getCounters(game).containsKey(CounterType.DISCOVERY) + && LaraCroftTombRaiderWatcher.checkPlayer(source.getControllerId(), game); + } +} + +class LaraCroftTombRaiderWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + LaraCroftTombRaiderWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + case LAND_PLAYED: + if (event.hasApprovingIdentifier(MageIdentifier.LaraCroftTombRaiderWatcher)) { + map.compute(event.getPlayerId(), (u, i) -> i == null ? 1 : Integer.sum(i, -1)); + } + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + static void incrementWatcher(UUID playerId, Game game) { + game.getState() + .getWatcher(LaraCroftTombRaiderWatcher.class) + .map + .compute(playerId, CardUtil::setOrIncrementValue); + } + + static boolean checkPlayer(UUID playerId, Game game) { + return game + .getState() + .getWatcher(LaraCroftTombRaiderWatcher.class) + .map + .getOrDefault(playerId, 0) > 0; + } +} + +class LaraCroftTombRaiderTriggeredAbility extends TriggeredAbilityImpl { + + LaraCroftTombRaiderTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new TreasureToken())); + setTriggerPhrase("At end of combat on your turn, if you attacked this turn, "); + this.addWatcher(new PlayerAttackedWatcher()); + this.setAbilityWord(AbilityWord.RAID); + } + + private LaraCroftTombRaiderTriggeredAbility(final LaraCroftTombRaiderTriggeredAbility ability) { + super(ability); + } + + @Override + public LaraCroftTombRaiderTriggeredAbility copy() { + return new LaraCroftTombRaiderTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_COMBAT_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.isActivePlayer(getControllerId()); + } + + @Override + public boolean checkInterveningIfClause(Game game) { + return RaidCondition.instance.apply(game, this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LavakinBrawler.java b/Mage.Sets/src/mage/cards/l/LavakinBrawler.java index 76aa965fc46..8ad3bde3edf 100644 --- a/Mage.Sets/src/mage/cards/l/LavakinBrawler.java +++ b/Mage.Sets/src/mage/cards/l/LavakinBrawler.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -21,7 +21,7 @@ import java.util.UUID; */ public final class LavakinBrawler extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.ELEMENTAL); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ELEMENTAL); private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); public LavakinBrawler(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/l/LayBare.java b/Mage.Sets/src/mage/cards/l/LayBare.java index 155d911aac2..83a9aaa49b3 100644 --- a/Mage.Sets/src/mage/cards/l/LayBare.java +++ b/Mage.Sets/src/mage/cards/l/LayBare.java @@ -55,7 +55,7 @@ class LayBareEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Card target = (Card) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK); + Card target = (Card) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK); if (target != null) { Player controller = game.getPlayer(target.getOwnerId()); if (controller != null && player != null) { diff --git a/Mage.Sets/src/mage/cards/l/LazavDimirMastermind.java b/Mage.Sets/src/mage/cards/l/LazavDimirMastermind.java index dabcb69b2c8..40aa6f4d101 100644 --- a/Mage.Sets/src/mage/cards/l/LazavDimirMastermind.java +++ b/Mage.Sets/src/mage/cards/l/LazavDimirMastermind.java @@ -85,7 +85,6 @@ class LazavDimirMastermindEffect extends OneShotEffect { CopyApplier applier = new LazavDimirMastermindCopyApplier(); applier.apply(game, newBluePrint, source, lazavDimirMastermind.getId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, newBluePrint, lazavDimirMastermind.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/l/LazavTheMultifarious.java b/Mage.Sets/src/mage/cards/l/LazavTheMultifarious.java index 556b436c269..cc44a44c7e8 100644 --- a/Mage.Sets/src/mage/cards/l/LazavTheMultifarious.java +++ b/Mage.Sets/src/mage/cards/l/LazavTheMultifarious.java @@ -115,7 +115,6 @@ class LazavTheMultifariousEffect extends OneShotEffect { CopyApplier applier = new LazavTheMultifariousCopyApplier(); applier.apply(game, newBluePrint, source, lazavTheMultifarious.getId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, newBluePrint, lazavTheMultifarious.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java b/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java new file mode 100644 index 00000000000..b3152bf3bee --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java @@ -0,0 +1,118 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class LazavWearerOfFaces extends CardImpl { + + public LazavWearerOfFaces(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SHAPESHIFTER); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever Lazav, Wearer of Faces attacks, exile target card from a graveyard, then investigate. + Ability ability = new AttacksTriggeredAbility(new ExileTargetEffect().setToSourceExileZone(true)); + ability.addEffect(new InvestigateEffect().concatBy(", then")); + ability.addTarget(new TargetCardInGraveyard()); + this.addAbility(ability); + + // Whenever you sacrifice a Clue, you may have Lazav become a copy of a creature card exiled with it until end of turn. + this.addAbility(new SacrificePermanentTriggeredAbility(new LazavWearerOfFacesEffect(), + StaticFilters.FILTER_CONTROLLED_CLUE)); + } + + private LazavWearerOfFaces(final LazavWearerOfFaces card) { + super(card); + } + + @Override + public LazavWearerOfFaces copy() { + return new LazavWearerOfFaces(this); + } +} + +class LazavWearerOfFacesEffect extends OneShotEffect { + + LazavWearerOfFacesEffect() { + super(Outcome.Copy); + staticText = "you may have {this} become a copy of a creature card exiled with it until end of turn"; + } + + private LazavWearerOfFacesEffect(final LazavWearerOfFacesEffect effect) { + super(effect); + } + + @Override + public LazavWearerOfFacesEffect copy() { + return new LazavWearerOfFacesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent lazav = game.getPermanent(source.getSourceId()); + if (controller == null || lazav == null) { + return false; + } + + if (!controller.chooseUse(outcome, "Have {this} become a copy of a creature card exiled with it", source, game)) { + return false; + } + + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile == null) { + return false; + } + Cards cards = new CardsImpl(exile.getCards(StaticFilters.FILTER_CARD_CREATURE, game)); + TargetCard target = new TargetCard(Zone.EXILED, new FilterCard("creature card to copy")); + target.withNotTarget(true); + controller.chooseTarget(outcome, cards, target, source, game); + + Card copyFromCard = game.getCard(target.getFirstTarget()); + if (copyFromCard == null) { + return false; + } + + CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, copyFromCard, lazav.getId()); + copyEffect.newId(); + game.addEffect(copyEffect, source); + + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LazotepConvert.java b/Mage.Sets/src/mage/cards/l/LazotepConvert.java index cdc4853098d..6c6bf532331 100644 --- a/Mage.Sets/src/mage/cards/l/LazotepConvert.java +++ b/Mage.Sets/src/mage/cards/l/LazotepConvert.java @@ -95,6 +95,7 @@ class LazotepConvertCopyEffect extends OneShotEffect { } Card modifiedCopy = copyFromCard.copy(); //Appliers must be applied before CopyEffect, its applier setting is just for copies of copies + // TODO: research applier usage, why it here applier.apply(game, modifiedCopy, source, source.getSourceId()); game.addEffect(new CopyEffect( Duration.Custom, modifiedCopy, source.getSourceId() diff --git a/Mage.Sets/src/mage/cards/l/LeaveNoTrace.java b/Mage.Sets/src/mage/cards/l/LeaveNoTrace.java index 497c1cebae7..0d414da28c9 100644 --- a/Mage.Sets/src/mage/cards/l/LeaveNoTrace.java +++ b/Mage.Sets/src/mage/cards/l/LeaveNoTrace.java @@ -62,7 +62,7 @@ class LeaveNoTraceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null) { ObjectColor color = target.getColor(game); target.destroy(source, game, false); diff --git a/Mage.Sets/src/mage/cards/l/LegionLoyalist.java b/Mage.Sets/src/mage/cards/l/LegionLoyalist.java index 6cd4fd68ec9..9f7b8851995 100644 --- a/Mage.Sets/src/mage/cards/l/LegionLoyalist.java +++ b/Mage.Sets/src/mage/cards/l/LegionLoyalist.java @@ -69,6 +69,7 @@ class LegionLoyalistCantBeBlockedByTokensEffect extends RestrictionEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); affectedObjectsSet = true; for (Permanent perm : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURES, source.getControllerId(), source, game)) { affectedObjectList.add(new MageObjectReference(perm, game)); diff --git a/Mage.Sets/src/mage/cards/l/LesserWerewolf.java b/Mage.Sets/src/mage/cards/l/LesserWerewolf.java index d32e5b5a589..5b0d51b003f 100644 --- a/Mage.Sets/src/mage/cards/l/LesserWerewolf.java +++ b/Mage.Sets/src/mage/cards/l/LesserWerewolf.java @@ -79,7 +79,7 @@ class LesserWerewolfEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); // must be valid target + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); // must be valid target if (controller == null || sourcePermanent == null || targetPermanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/l/LibertyPrimeRecharged.java b/Mage.Sets/src/mage/cards/l/LibertyPrimeRecharged.java new file mode 100644 index 00000000000..5f26494aa17 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LibertyPrimeRecharged.java @@ -0,0 +1,67 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksOrBlocksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LibertyPrimeRecharged extends CardImpl { + + public LibertyPrimeRecharged(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{U}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Liberty Prime, Recharged attacks or blocks, sacrifice it unless you pay {E}{E}. + this.addAbility(new AttacksOrBlocksTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new PayEnergyCost(2)), false)); + + // {2}, {T}, Sacrifice an artifact: You get {E}{E} and draw a card. + Ability ability = new SimpleActivatedAbility(new GetEnergyCountersControllerEffect(2).setText("you get {E}{E}"), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private LibertyPrimeRecharged(final LibertyPrimeRecharged card) { + super(card); + } + + @Override + public LibertyPrimeRecharged copy() { + return new LibertyPrimeRecharged(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LiegeOfTheTangle.java b/Mage.Sets/src/mage/cards/l/LiegeOfTheTangle.java index 2f64b1c4644..764568e9dd5 100644 --- a/Mage.Sets/src/mage/cards/l/LiegeOfTheTangle.java +++ b/Mage.Sets/src/mage/cards/l/LiegeOfTheTangle.java @@ -107,7 +107,7 @@ class LiegeOfTheTangleEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); if (this.affectedObjectsSet) { - for (UUID permId : targetPointer.getTargets(game, source)) { + for (UUID permId : getTargetPointer().getTargets(game, source)) { affectedObjectList.add(new MageObjectReference(permId, game)); } } diff --git a/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java b/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java index 8a8f18d9aff..518a98ce57a 100644 --- a/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java +++ b/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java @@ -92,7 +92,7 @@ class LiesaForgottenArchangelReturnToHandEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Effect effect = new ReturnToHandTargetEffect(); effect.setText("return that card to its owner's hand"); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); DelayedTriggeredAbility ability = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); game.addDelayedTriggeredAbility(ability, source); return true; diff --git a/Mage.Sets/src/mage/cards/l/LightningBlow.java b/Mage.Sets/src/mage/cards/l/LightningBlow.java index 6dc80e2a7fa..5648b52c76f 100644 --- a/Mage.Sets/src/mage/cards/l/LightningBlow.java +++ b/Mage.Sets/src/mage/cards/l/LightningBlow.java @@ -28,7 +28,7 @@ public final class LightningBlow extends CardImpl { // Draw a card at the beginning of the next turn's upkeep. this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect( - new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1), Duration.OneUse), false)); + new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1), Duration.OneUse), false).concatBy("
    ")); } private LightningBlow(final LightningBlow card) { diff --git a/Mage.Sets/src/mage/cards/l/LightningDart.java b/Mage.Sets/src/mage/cards/l/LightningDart.java index 6e4d9c0afab..05b1a28aada 100644 --- a/Mage.Sets/src/mage/cards/l/LightningDart.java +++ b/Mage.Sets/src/mage/cards/l/LightningDart.java @@ -54,7 +54,7 @@ public final class LightningDart extends CardImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { int damage = 1; ObjectColor color = permanent.getColor(game); diff --git a/Mage.Sets/src/mage/cards/l/LightningRunner.java b/Mage.Sets/src/mage/cards/l/LightningRunner.java index 738cf566133..bbe15e87b4c 100644 --- a/Mage.Sets/src/mage/cards/l/LightningRunner.java +++ b/Mage.Sets/src/mage/cards/l/LightningRunner.java @@ -1,4 +1,3 @@ - package mage.cards.l; import java.util.UUID; @@ -19,7 +18,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -82,7 +81,7 @@ class LightningRunnerEffect extends OneShotEffect { "Untap all creatures you control and after this phase, there is an additional combat phase.", "Yes", "No", source, game) && cost.pay(source, game, source, source.getControllerId(), true)) { - new UntapAllControllerEffect(new FilterControlledCreaturePermanent()).apply(game, source); + new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURES).apply(game, source); new AdditionalCombatPhaseEffect().apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/l/LikenessLooter.java b/Mage.Sets/src/mage/cards/l/LikenessLooter.java index 157a059e378..fd91545c71b 100644 --- a/Mage.Sets/src/mage/cards/l/LikenessLooter.java +++ b/Mage.Sets/src/mage/cards/l/LikenessLooter.java @@ -110,7 +110,6 @@ class LikenessLooterEffect extends OneShotEffect { CopyApplier applier = new LikenessLooterCopyApplier(); applier.apply(game, newBluePrint, source, permanent.getId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, newBluePrint, permanent.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/l/LimDulTheNecromancer.java b/Mage.Sets/src/mage/cards/l/LimDulTheNecromancer.java index 482ccbda693..a5ad9c70afb 100644 --- a/Mage.Sets/src/mage/cards/l/LimDulTheNecromancer.java +++ b/Mage.Sets/src/mage/cards/l/LimDulTheNecromancer.java @@ -84,7 +84,7 @@ class LimDulTheNecromancerEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { if (controller.moveCards(card, Zone.BATTLEFIELD, source, game) && card.isCreature(game)) { diff --git a/Mage.Sets/src/mage/cards/l/LinvalaShieldOfSeaGate.java b/Mage.Sets/src/mage/cards/l/LinvalaShieldOfSeaGate.java index 52797ea1b2c..e21f5638289 100644 --- a/Mage.Sets/src/mage/cards/l/LinvalaShieldOfSeaGate.java +++ b/Mage.Sets/src/mage/cards/l/LinvalaShieldOfSeaGate.java @@ -99,7 +99,7 @@ class LinvalaShieldOfSeaGateRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/l/LiquidFire.java b/Mage.Sets/src/mage/cards/l/LiquidFire.java index 00838592a50..76247f1b5bb 100644 --- a/Mage.Sets/src/mage/cards/l/LiquidFire.java +++ b/Mage.Sets/src/mage/cards/l/LiquidFire.java @@ -60,7 +60,7 @@ public final class LiquidFire extends CardImpl { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); int creatureDamage = choiceValue.calculate(game, source, this); int playerDamage = 5 - creatureDamage; if (target != null) { diff --git a/Mage.Sets/src/mage/cards/l/LithomancersFocus.java b/Mage.Sets/src/mage/cards/l/LithomancersFocus.java index 47eabaf867e..32ec68a6ca1 100644 --- a/Mage.Sets/src/mage/cards/l/LithomancersFocus.java +++ b/Mage.Sets/src/mage/cards/l/LithomancersFocus.java @@ -59,7 +59,7 @@ class LithomancersFocusPreventDamageToTargetEffect extends PreventionEffectImpl @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (super.applies(event, source, game) && event.getTargetId().equals(targetPointer.getFirst(game, source))) { + if (super.applies(event, source, game) && event.getTargetId().equals(getTargetPointer().getFirst(game, source))) { MageObject object = game.getObject(event.getSourceId()); return object != null && object.getColor(game).isColorless(); } diff --git a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java index 62652921057..98e5d355381 100644 --- a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java +++ b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java @@ -82,12 +82,11 @@ class LocusOfEnlightenmentEffect extends ContinuousEffectImpl { } for (Card card : exileZone.getCards(game)) { for (Ability ability : card.getAbilities(game)) { - if (!(ability instanceof ActivatedAbility)) { - continue; + if (ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.MANA) { + ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); + copyAbility.setMaxActivationsPerTurn(1); + permanent.addAbility(copyAbility, source.getSourceId(), game); } - ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); - copyAbility.setMaxActivationsPerTurn(1); - permanent.addAbility(copyAbility, source.getSourceId(), game); } } return true; diff --git a/Mage.Sets/src/mage/cards/l/LongRest.java b/Mage.Sets/src/mage/cards/l/LongRest.java index a19c653e228..2efba798956 100644 --- a/Mage.Sets/src/mage/cards/l/LongRest.java +++ b/Mage.Sets/src/mage/cards/l/LongRest.java @@ -115,7 +115,7 @@ class LongRestEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Set cardsToHand = new HashSet<>(); - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Card card = game.getCard(targetId); if (card != null && game.getState().getZone(targetId) == Zone.GRAVEYARD) { cardsToHand.add(card); diff --git a/Mage.Sets/src/mage/cards/l/LonisGeneticsExpert.java b/Mage.Sets/src/mage/cards/l/LonisGeneticsExpert.java new file mode 100644 index 00000000000..c4a07568f31 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LonisGeneticsExpert.java @@ -0,0 +1,88 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.OneOrMoreCountersAddedTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.EvolveAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class LonisGeneticsExpert extends CardImpl { + + public LonisGeneticsExpert(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G/U}{G/U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Evolve + this.addAbility(new EvolveAbility()); + + // Whenever one or more +1/+1 counters are put on Lonis, investigate that many times. + this.addAbility(new LonisGeneticsExpertTriggeredAbility()); + + // Whenever you sacrifice a Clue, put a +1/+1 counter on another target creature you control. + Ability ability = new SacrificePermanentTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_CONTROLLED_CLUE + ); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private LonisGeneticsExpert(final LonisGeneticsExpert card) { + super(card); + } + + @Override + public LonisGeneticsExpert copy() { + return new LonisGeneticsExpert(this); + } +} + +class LonisGeneticsExpertTriggeredAbility extends OneOrMoreCountersAddedTriggeredAbility { + + LonisGeneticsExpertTriggeredAbility() { + super(new InvestigateEffect(SavedDamageValue.MANY).setText("investigate that many times")); + } + + private LonisGeneticsExpertTriggeredAbility(final LonisGeneticsExpertTriggeredAbility ability) { + super(ability); + } + + @Override + public LonisGeneticsExpertTriggeredAbility copy() { + return new LonisGeneticsExpertTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + this.getEffects().setValue("damage", event.getAmount()); + return true; + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/l/LordOfShatterskullPass.java b/Mage.Sets/src/mage/cards/l/LordOfShatterskullPass.java index 8c009824f8f..44bed612ae5 100644 --- a/Mage.Sets/src/mage/cards/l/LordOfShatterskullPass.java +++ b/Mage.Sets/src/mage/cards/l/LordOfShatterskullPass.java @@ -1,26 +1,21 @@ - package mage.cards.l; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllControlledTargetEffect; import mage.abilities.keyword.LevelUpAbility; import mage.abilities.keyword.LevelerCardBuilder; import mage.cards.CardSetInfo; import mage.cards.LevelerCard; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; + +import java.util.UUID; /** * @@ -46,7 +41,9 @@ public final class LordOfShatterskullPass extends LevelerCard { // 6/6 // Whenever Lord of Shatterskull Pass attacks, it deals 6 damage to each creature defending player controls. Abilities abilities2 = new AbilitiesImpl<>(); - abilities2.add(new AttacksTriggeredAbility(new LordOfShatterskullPassEffect(), false)); + abilities2.add(new AttacksTriggeredAbility(new DamageAllControlledTargetEffect(6) + .setText("it deals 6 damage to each creature defending player controls"), + false, null, SetTargetPointer.PLAYER)); this.addAbilities(LevelerCardBuilder.construct( new LevelerCardBuilder.LevelAbility(1, 5, abilities1, 6, 6), @@ -63,35 +60,3 @@ public final class LordOfShatterskullPass extends LevelerCard { return new LordOfShatterskullPass(this); } } - -class LordOfShatterskullPassEffect extends OneShotEffect { - - LordOfShatterskullPassEffect() { - super(Outcome.Damage); - this.staticText = "it deals 6 damage to each creature defending player controls"; - } - - private LordOfShatterskullPassEffect(final LordOfShatterskullPassEffect effect) { - super(effect); - } - - @Override - public LordOfShatterskullPassEffect copy() { - return new LordOfShatterskullPassEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - UUID defenderId = game.getCombat().getDefenderId(source.getSourceId()); - if (defenderId != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent(); - filter.add(new ControllerIdPredicate(defenderId)); - List permanents = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game); - for (Permanent permanent : permanents) { - permanent.damage(6, source.getSourceId(), source, game, false, true); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LordOfTheNazgul.java b/Mage.Sets/src/mage/cards/l/LordOfTheNazgul.java index 1df8cbecb49..ce07d072a9b 100644 --- a/Mage.Sets/src/mage/cards/l/LordOfTheNazgul.java +++ b/Mage.Sets/src/mage/cards/l/LordOfTheNazgul.java @@ -20,7 +20,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.RingBearerPredicate; import mage.game.permanent.token.WraithToken; @@ -31,7 +31,7 @@ import java.util.UUID; */ public final class LordOfTheNazgul extends CardImpl { - private static final FilterControlledCreaturePermanent filterWraith = new FilterControlledCreaturePermanent("Wraiths you control"); + private static final FilterPermanent filterWraith = new FilterControlledPermanent("Wraiths you control"); private static final FilterPermanent filterRingBearer = new FilterPermanent("Ring-bearers"); private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filterWraith, ComparisonType.MORE_THAN, 8); private static final Hint hint = new ValueHint( diff --git a/Mage.Sets/src/mage/cards/l/LordSkittersBlessing.java b/Mage.Sets/src/mage/cards/l/LordSkittersBlessing.java index 37962c84b22..7f11e583714 100644 --- a/Mage.Sets/src/mage/cards/l/LordSkittersBlessing.java +++ b/Mage.Sets/src/mage/cards/l/LordSkittersBlessing.java @@ -35,7 +35,7 @@ public final class LordSkittersBlessing extends CardImpl { } private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); - private static final Hint hint = new ConditionHint(condition, "You control an enchanted creaeture"); + private static final Hint hint = new ConditionHint(condition, "You control an enchanted creature"); public LordSkittersBlessing(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); @@ -53,7 +53,7 @@ public final class LordSkittersBlessing extends CardImpl { "an enchanted creature, you lose 1 life and you draw an additional card." ); ability.addEffect(new DrawCardSourceControllerEffect(1)); - this.addAbility(ability); + this.addAbility(ability.addHint(hint)); } private LordSkittersBlessing(final LordSkittersBlessing card) { diff --git a/Mage.Sets/src/mage/cards/l/LostHours.java b/Mage.Sets/src/mage/cards/l/LostHours.java index ad0956e50af..2153287c52e 100644 --- a/Mage.Sets/src/mage/cards/l/LostHours.java +++ b/Mage.Sets/src/mage/cards/l/LostHours.java @@ -59,7 +59,7 @@ class LostHoursEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (targetPlayer != null && controller != null) { targetPlayer.revealCards(source, targetPlayer.getHand(), game); diff --git a/Mage.Sets/src/mage/cards/l/LostInTheWoods.java b/Mage.Sets/src/mage/cards/l/LostInTheWoods.java index dee42dc9d61..c5b2e0959b3 100644 --- a/Mage.Sets/src/mage/cards/l/LostInTheWoods.java +++ b/Mage.Sets/src/mage/cards/l/LostInTheWoods.java @@ -62,7 +62,7 @@ class LostInTheWoodsEffect extends OneShotEffect { if (card != null) { if (card.hasSubtype(SubType.FOREST, game)) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.removeFromCombat(game); } diff --git a/Mage.Sets/src/mage/cards/l/LostLegacy.java b/Mage.Sets/src/mage/cards/l/LostLegacy.java index 09ba47fb7db..6d2eb43b7d0 100644 --- a/Mage.Sets/src/mage/cards/l/LostLegacy.java +++ b/Mage.Sets/src/mage/cards/l/LostLegacy.java @@ -59,7 +59,7 @@ class LostLegacyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExi FilterCard filter = new FilterCard(); filter.add(new NamePredicate(cardName)); int cardsInHandBefore = targetPlayer.getHand().count(filter, game); - boolean result = super.applySearchAndExile(game, source, cardName, targetPointer.getFirst(game, source)); + boolean result = super.applySearchAndExile(game, source, cardName, getTargetPointer().getFirst(game, source)); int cardsExiled = cardsInHandBefore - targetPlayer.getHand().count(filter, game); if (cardsExiled > 0) { targetPlayer.drawCards(cardsExiled, source, game); diff --git a/Mage.Sets/src/mage/cards/l/LoxodonPeacekeeper.java b/Mage.Sets/src/mage/cards/l/LoxodonPeacekeeper.java index fa7973b3dac..fe5dceb3b7d 100644 --- a/Mage.Sets/src/mage/cards/l/LoxodonPeacekeeper.java +++ b/Mage.Sets/src/mage/cards/l/LoxodonPeacekeeper.java @@ -1,4 +1,3 @@ - package mage.cards.l; import java.util.HashSet; @@ -95,7 +94,7 @@ class LoxodonPeacekeeperEffect extends OneShotEffect { } } - if (tiedPlayers.size() > 0) { + if (!tiedPlayers.isEmpty()) { UUID newControllerId = null; if (tiedPlayers.size() > 1) { FilterPlayer filter = new FilterPlayer("a player tied for lowest life total"); diff --git a/Mage.Sets/src/mage/cards/l/LullmageMentor.java b/Mage.Sets/src/mage/cards/l/LullmageMentor.java index 8bde38976d9..d32a02ca48f 100644 --- a/Mage.Sets/src/mage/cards/l/LullmageMentor.java +++ b/Mage.Sets/src/mage/cards/l/LullmageMentor.java @@ -1,4 +1,3 @@ - package mage.cards.l; import java.util.UUID; @@ -14,11 +13,11 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.permanent.token.MerfolkToken; import mage.target.TargetSpell; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -26,7 +25,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class LullmageMentor extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Merfolk you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Merfolk you control"); static { filter.add(SubType.MERFOLK.getPredicate()); @@ -45,7 +44,7 @@ public final class LullmageMentor extends CardImpl { this.addAbility(new SpellCounteredControllerTriggeredAbility(new CreateTokenEffect(new MerfolkToken()), true)); // Tap seven untapped Merfolk you control: Counter target spell. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CounterTargetEffect(), new TapTargetCost(new TargetControlledCreaturePermanent(7, 7, filter, true))); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CounterTargetEffect(), new TapTargetCost(new TargetControlledPermanent(7, 7, filter, true))); ability.addTarget(new TargetSpell()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/l/LumraBellowOfTheWoods.java b/Mage.Sets/src/mage/cards/l/LumraBellowOfTheWoods.java new file mode 100644 index 00000000000..32992983b9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LumraBellowOfTheWoods.java @@ -0,0 +1,87 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LumraBellowOfTheWoods extends CardImpl { + + public LumraBellowOfTheWoods(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.BEAR); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Lumra, Bellow of the Woods's power and toughness are each equal to the number of lands you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerToughnessSourceEffect(LandsYouControlCount.instance) + )); + + // When Lumra enters, mill four cards. Then return all land cards from your graveyard to the battlefield tapped. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(4)); + ability.addEffect(new LumraBellowOfTheWoodsEffect()); + this.addAbility(ability); + } + + private LumraBellowOfTheWoods(final LumraBellowOfTheWoods card) { + super(card); + } + + @Override + public LumraBellowOfTheWoods copy() { + return new LumraBellowOfTheWoods(this); + } +} + +class LumraBellowOfTheWoodsEffect extends OneShotEffect { + + LumraBellowOfTheWoodsEffect() { + super(Outcome.Benefit); + staticText = "Then return all land cards from your graveyard to the battlefield tapped"; + } + + private LumraBellowOfTheWoodsEffect(final LumraBellowOfTheWoodsEffect effect) { + super(effect); + } + + @Override + public LumraBellowOfTheWoodsEffect copy() { + return new LumraBellowOfTheWoodsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.moveCards( + player.getGraveyard().getCards(StaticFilters.FILTER_CARD_LAND, game), + Zone.BATTLEFIELD, source, game, true, false, false, null + ); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuxArtillery.java b/Mage.Sets/src/mage/cards/l/LuxArtillery.java index 35f2725752e..cc3b5dfa2b9 100644 --- a/Mage.Sets/src/mage/cards/l/LuxArtillery.java +++ b/Mage.Sets/src/mage/cards/l/LuxArtillery.java @@ -86,7 +86,7 @@ class LuxArtilleryEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); - Spell object = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell object = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (object != null) { zoneChangeCounter = game.getState().getZoneChangeCounter(object.getSourceId()) + 1; permanentId = object.getSourceId(); @@ -102,7 +102,7 @@ class LuxArtilleryEffect extends ContinuousEffectImpl { if (game.getState().getZoneChangeCounter(permanentId) >= zoneChangeCounter) { discard(); } - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { game.getState().addOtherAbility(spell.getCard(), ability, true); } diff --git a/Mage.Sets/src/mage/cards/m/MabelHeirToCragflame.java b/Mage.Sets/src/mage/cards/m/MabelHeirToCragflame.java new file mode 100644 index 00000000000..0bb3feabec5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MabelHeirToCragflame.java @@ -0,0 +1,51 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.CragflameToken; + +/** + * + * @author TheElk801 + */ +public final class MabelHeirToCragflame extends CardImpl { + + private static final FilterCreaturePermanent filter=new FilterCreaturePermanent(SubType.MOUSE,"Mice"); + public MabelHeirToCragflame(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MOUSE); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Other Mice you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 1,1, Duration.WhileOnBattlefield,filter,true + ))); + + // When Mabel, Heir to Cragflame enters, create Cragflame, a legendary colorless Equipment artifact token with "Equipped creature gets +1/+1 and has vigilance, trample, and haste" and equip {2}. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new CragflameToken()))); + } + + private MabelHeirToCragflame(final MabelHeirToCragflame card) { + super(card); + } + + @Override + public MabelHeirToCragflame copy() { + return new MabelHeirToCragflame(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MacCreadyLamplightMayor.java b/Mage.Sets/src/mage/cards/m/MacCreadyLamplightMayor.java new file mode 100644 index 00000000000..8c830909ea1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MacCreadyLamplightMayor.java @@ -0,0 +1,73 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttackedByCreatureTriggeredAbility; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.SkulkAbility; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +/** + * @author Cguy7777 + */ +public final class MacCreadyLamplightMayor extends CardImpl { + + private static final FilterControlledCreaturePermanent filterTwoOrLess + = new FilterControlledCreaturePermanent("creature you control with power 2 or less"); + private static final FilterCreaturePermanent filterFourOrGreater + = new FilterCreaturePermanent("creature with power 4 or greater"); + + static { + filterTwoOrLess.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3)); + filterFourOrGreater.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public MacCreadyLamplightMayor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Whenever a creature you control with power 2 or less attacks, it gains skulk until end of turn. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility( + new GainAbilityTargetEffect( + new SkulkAbility(), + Duration.EndOfTurn, + "it gains skulk until end of turn. (It can't be blocked by creatures with greater power.)"), + false, + filterTwoOrLess, + true)); + + // Whenever a creature with power 4 or greater attacks you, its controller loses 2 life and you gain 2 life. + Ability ability = new AttackedByCreatureTriggeredAbility( + Zone.BATTLEFIELD, + new LoseLifeTargetEffect(2).setText("its controller loses 2 life"), + false, + SetTargetPointer.PLAYER, + filterFourOrGreater); + ability.addEffect(new GainLifeEffect(2).concatBy("and")); + this.addAbility(ability); + } + + private MacCreadyLamplightMayor(final MacCreadyLamplightMayor card) { + super(card); + } + + @Override + public MacCreadyLamplightMayor copy() { + return new MacCreadyLamplightMayor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MaddeningHex.java b/Mage.Sets/src/mage/cards/m/MaddeningHex.java index cfaa6bfdc83..a21db5ed057 100644 --- a/Mage.Sets/src/mage/cards/m/MaddeningHex.java +++ b/Mage.Sets/src/mage/cards/m/MaddeningHex.java @@ -127,7 +127,7 @@ class MaddeningHexEffect extends OneShotEffect { if (permanent == null) { return true; } - Set opponents = game.getOpponents(source.getControllerId()); + Set opponents = game.getOpponents(source.getControllerId(), true); if (player != null) { opponents.remove(player.getId()); diff --git a/Mage.Sets/src/mage/cards/m/MagneticMountain.java b/Mage.Sets/src/mage/cards/m/MagneticMountain.java index ee77a0e8615..5daf2930657 100644 --- a/Mage.Sets/src/mage/cards/m/MagneticMountain.java +++ b/Mage.Sets/src/mage/cards/m/MagneticMountain.java @@ -81,7 +81,7 @@ class MagneticMountainEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && sourcePermanent != null) { int countBattlefield = game.getBattlefield().getAllActivePermanents(filter2, game.getActivePlayerId(), game).size(); diff --git a/Mage.Sets/src/mage/cards/m/MagneticSnuffler.java b/Mage.Sets/src/mage/cards/m/MagneticSnuffler.java new file mode 100644 index 00000000000..5af46b9d564 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagneticSnuffler.java @@ -0,0 +1,89 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author DominionSpy + */ +public final class MagneticSnuffler extends CardImpl { + + private static final FilterCard filter = new FilterCard("Equipment card from your graveyard"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + public MagneticSnuffler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}"); + + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Magnetic Snuffler enters the battlefield, return target Equipment card from your graveyard to the battlefield attached to Magnetic Snuffler. + Ability ability = new EntersBattlefieldTriggeredAbility(new MagneticSnufflerEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Whenever you sacrifice an artifact, put a +1/+1 counter on Magnetic Snuffler. + this.addAbility(new SacrificePermanentTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_PERMANENT_ARTIFACT)); + } + + private MagneticSnuffler(final MagneticSnuffler card) { + super(card); + } + + @Override + public MagneticSnuffler copy() { + return new MagneticSnuffler(this); + } +} + +class MagneticSnufflerEffect extends ReturnFromGraveyardToBattlefieldTargetEffect { + + MagneticSnufflerEffect() { + super(); + staticText = "return target Equipment card from your graveyard to the battlefield attached to {this}"; + } + + private MagneticSnufflerEffect(final MagneticSnufflerEffect effect) { + super(effect); + } + + @Override + public MagneticSnufflerEffect copy() { + return new MagneticSnufflerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + super.apply(game, source); + + Permanent equipment = game.getPermanent(source.getFirstTarget()); + Permanent creature = game.getPermanent(source.getSourceId()); + if (equipment == null || creature == null) { + return false; + } + + return creature.addAttachment(equipment.getId(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java b/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java index 49311ee1c14..d0149959ca0 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java @@ -13,7 +13,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; @@ -38,7 +38,7 @@ public final class MagusOfTheArena extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MagusOfTheArenaEffect(), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetControlledCreaturePermanent()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java b/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java index cf5201b1bc4..e81d8b88672 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheScroll.java @@ -77,12 +77,12 @@ class MagusOfTheScrollEffect extends OneShotEffect { revealed.add(card); you.revealCards(sourceObject.getName(), revealed, game); if (CardUtil.haveSameNames(card, cardName, game)) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature != null) { creature.damage(2, source.getSourceId(), source, game, false, true); return true; } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(2, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java index af52c6f5257..d37cd5621a9 100644 --- a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java +++ b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java @@ -15,6 +15,7 @@ import mage.abilities.keyword.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; @@ -34,7 +35,7 @@ public final class MajesticMyriarch extends CardImpl { this.toughness = new MageInt(0); // Majestic Myriarch's power and toughness are each equal to twice the number of creatures you control. - DynamicValue xValue = new MultipliedValue(new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent()), 2); + DynamicValue xValue = new MultipliedValue(new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURES), 2); Effect effect = new SetBasePowerToughnessSourceEffect(xValue); effect.setText("{this}'s power and toughness are each equal to twice the number of creatures you control"); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect)); @@ -163,4 +164,4 @@ class MajesticMyriarchEffect extends OneShotEffect { } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java b/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java index 59c1dcd3ca3..149c493b4bd 100644 --- a/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java +++ b/Mage.Sets/src/mage/cards/m/MalakirSoothsayer.java @@ -1,22 +1,16 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; -import mage.abilities.effects.Effect; +import mage.abilities.Ability; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** * @@ -24,13 +18,6 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class MalakirSoothsayer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public MalakirSoothsayer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}"); this.subtype.add(SubType.VAMPIRE); @@ -40,13 +27,8 @@ public final class MalakirSoothsayer extends CardImpl { this.toughness = new MageInt(4); // Cohort — {T}, Tap an untapped Ally you control: You draw a card and you lose a life. - SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new DrawCardSourceControllerEffect(1).setText("you draw a card"), - new TapSourceCost()); - ability.setAbilityWord(AbilityWord.COHORT); - ability.addCost(new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false))); - Effect effect = new LoseLifeSourceControllerEffect(1).concatBy("and"); - ability.addEffect(effect); + Ability ability = new CohortAbility(new DrawCardSourceControllerEffect(1, "you")); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/ManaBreach.java b/Mage.Sets/src/mage/cards/m/ManaBreach.java index 706a3921b9d..c0fc44852cb 100644 --- a/Mage.Sets/src/mage/cards/m/ManaBreach.java +++ b/Mage.Sets/src/mage/cards/m/ManaBreach.java @@ -58,7 +58,7 @@ class ManaBreachEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null || game.getBattlefield().count( StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, player.getId(), source, game diff --git a/Mage.Sets/src/mage/cards/m/MandateOfPeace.java b/Mage.Sets/src/mage/cards/m/MandateOfPeace.java index bb234bf6228..a61bdd26217 100644 --- a/Mage.Sets/src/mage/cards/m/MandateOfPeace.java +++ b/Mage.Sets/src/mage/cards/m/MandateOfPeace.java @@ -36,7 +36,7 @@ public final class MandateOfPeace extends CardImpl { this.getSpellAbility().addEffect(new MandateOfPeaceOpponentsCantCastSpellsEffect()); // End the combat phase. - this.getSpellAbility().addEffect(new MandateOfPeaceEndCombatEffect()); + this.getSpellAbility().addEffect(new MandateOfPeaceEndCombatEffect().concatBy("
    ")); } private MandateOfPeace(final MandateOfPeace card) { diff --git a/Mage.Sets/src/mage/cards/m/MaraudingRaptor.java b/Mage.Sets/src/mage/cards/m/MaraudingRaptor.java index 34eb561ec33..947004023ea 100644 --- a/Mage.Sets/src/mage/cards/m/MaraudingRaptor.java +++ b/Mage.Sets/src/mage/cards/m/MaraudingRaptor.java @@ -70,7 +70,7 @@ class MaraudingRaptorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java index 130434ac05f..f0891a7d7a5 100644 --- a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java +++ b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java @@ -125,7 +125,7 @@ class MarchesaTheBlackRoseEffect extends OneShotEffect { Effect effect = new ReturnToBattlefieldUnderYourControlTargetEffect(); effect.setText("return that card to the battlefield under your control at the beginning of the next end step"); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); - delayedAbility.getEffects().get(0).setTargetPointer(getTargetPointer()); + delayedAbility.getEffects().get(0).setTargetPointer(this.getTargetPointer().copy()); game.addDelayedTriggeredAbility(delayedAbility, source); return true; } diff --git a/Mage.Sets/src/mage/cards/m/MarduOutrider.java b/Mage.Sets/src/mage/cards/m/MarduOutrider.java new file mode 100644 index 00000000000..4f668a90a16 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarduOutrider.java @@ -0,0 +1,38 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; // not sure about this line - think it is not needed +import mage.abilities.costs.common.DiscardCardCost; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author tiera3 - modified from LesserMasticore + */ +public final class MarduOutrider extends CardImpl { + + public MarduOutrider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // As an additional cost to cast this spell, discard a card. + this.getSpellAbility().addCost(new DiscardCardCost()); + } + + private MarduOutrider(final MarduOutrider card) { + super(card); + } + + @Override + public MarduOutrider copy() { + return new MarduOutrider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarkOfTheOni.java b/Mage.Sets/src/mage/cards/m/MarkOfTheOni.java index f3dfbc10505..a2a07f0e2d0 100644 --- a/Mage.Sets/src/mage/cards/m/MarkOfTheOni.java +++ b/Mage.Sets/src/mage/cards/m/MarkOfTheOni.java @@ -14,7 +14,7 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -44,7 +44,7 @@ public final class MarkOfTheOni extends CardImpl { new SacrificeSourceEffect(), TargetController.NEXT, new PermanentsOnTheBattlefieldCondition( - new FilterControlledCreaturePermanent(SubType.DEMON, "if you control no Demons"), + new FilterControlledPermanent(SubType.DEMON, "if you control no Demons"), ComparisonType.FEWER_THAN, 1), false)); } diff --git a/Mage.Sets/src/mage/cards/m/MarkovCrusader.java b/Mage.Sets/src/mage/cards/m/MarkovCrusader.java index a577b51faf3..ecbdd0e46fe 100644 --- a/Mage.Sets/src/mage/cards/m/MarkovCrusader.java +++ b/Mage.Sets/src/mage/cards/m/MarkovCrusader.java @@ -1,4 +1,3 @@ - package mage.cards.m; import java.util.UUID; @@ -15,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -25,7 +24,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; public final class MarkovCrusader extends CardImpl { private static final String rule = "{this} has haste as long as you control another Vampire"; - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Vampire"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another Vampire"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/m/MarthaJones.java b/Mage.Sets/src/mage/cards/m/MarthaJones.java index 49a0f45faa1..08363a2f3ee 100644 --- a/Mage.Sets/src/mage/cards/m/MarthaJones.java +++ b/Mage.Sets/src/mage/cards/m/MarthaJones.java @@ -14,7 +14,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCreaturePermanent; @@ -26,7 +26,6 @@ import java.util.UUID; */ public final class MarthaJones extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "a Clue"); private static final FilterCreaturePermanent filterOther = new FilterCreaturePermanent("other target creature"); static { @@ -49,7 +48,7 @@ public final class MarthaJones extends CardImpl { // Whenever you sacrifice a Clue, Martha Jones and up to one other target creature can't be blocked this turn. Ability ability = new SacrificePermanentTriggeredAbility( - new CantBeBlockedSourceEffect(Duration.EndOfTurn).setText("{this}"), filter + new CantBeBlockedSourceEffect(Duration.EndOfTurn).setText("{this}"), StaticFilters.FILTER_CONTROLLED_CLUE ); ability.addEffect(new CantBeBlockedTargetEffect() .setText("and up to one other target creature can't be blocked this turn")); diff --git a/Mage.Sets/src/mage/cards/m/MaskwoodNexus.java b/Mage.Sets/src/mage/cards/m/MaskwoodNexus.java index 5e0feb6a851..57621cbad9c 100644 --- a/Mage.Sets/src/mage/cards/m/MaskwoodNexus.java +++ b/Mage.Sets/src/mage/cards/m/MaskwoodNexus.java @@ -11,7 +11,7 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.command.CommandObject; import mage.game.command.Commander; @@ -130,7 +130,7 @@ class MaskwoodNexusEffect extends ContinuousEffectImpl { } // creatures you control List creatures = game.getBattlefield().getAllActivePermanents( - new FilterControlledCreaturePermanent(), source.getControllerId(), game); + StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game); for (Permanent creature : creatures) { if (creature != null) { creature.setIsAllCreatureTypes(game, true); diff --git a/Mage.Sets/src/mage/cards/m/MasterApothecary.java b/Mage.Sets/src/mage/cards/m/MasterApothecary.java index 89a5c0329c8..1032d7eb069 100644 --- a/Mage.Sets/src/mage/cards/m/MasterApothecary.java +++ b/Mage.Sets/src/mage/cards/m/MasterApothecary.java @@ -1,4 +1,3 @@ - package mage.cards.m; import java.util.UUID; @@ -13,10 +12,10 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetControlledPermanent; /** * @@ -24,7 +23,7 @@ import mage.target.common.TargetAnyTarget; */ public final class MasterApothecary extends CardImpl { - static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("an untapped Cleric you control"); + static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Cleric you control"); static { filter.add(SubType.CLERIC.getPredicate()); @@ -41,8 +40,8 @@ public final class MasterApothecary extends CardImpl { // Tap an untapped Cleric you control: Prevent the next 2 damage that would be dealt to any target this turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new PreventDamageToTargetEffect(Duration.EndOfTurn, 2), - new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, true))); + new PreventDamageToTargetEffect(Duration.EndOfTurn, 2), + new TapTargetCost(new TargetControlledPermanent(filter))); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MathasFiendSeeker.java b/Mage.Sets/src/mage/cards/m/MathasFiendSeeker.java index 770924e5af9..6aeecef0b48 100644 --- a/Mage.Sets/src/mage/cards/m/MathasFiendSeeker.java +++ b/Mage.Sets/src/mage/cards/m/MathasFiendSeeker.java @@ -78,7 +78,7 @@ class MathasFiendSeekerGainAbilityEffect extends GainAbilityTargetEffect { @Override public boolean isInactive(Ability source, Game game) { - Permanent creature = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (creature != null && creature.getCounters(game).getCount(CounterType.BOUNTY) < 1) { return true; } diff --git a/Mage.Sets/src/mage/cards/m/MeTheImmortal.java b/Mage.Sets/src/mage/cards/m/MeTheImmortal.java new file mode 100644 index 00000000000..412ac09d20e --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeTheImmortal.java @@ -0,0 +1,180 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.StaticAbility; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.counter.AddCounterChoiceSourceEffect; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.Counter; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.common.TargetCardInHand; + +/** + * + * @author Skiwkr + */ +public final class MeTheImmortal extends CardImpl { + + public MeTheImmortal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, put your choice of a +1/+1, first strike, vigilance, or menace counter on Me, the Immortal. + this.addAbility(new BeginningOfCombatTriggeredAbility(new AddCounterChoiceSourceEffect( + CounterType.P1P1, CounterType.FIRST_STRIKE, CounterType.VIGILANCE, CounterType.MENACE + ).setText("put your choice of a +1/+1, first strike, vigilance, or menace counter on {this}"), + TargetController.YOU, + false)); + // Counters remain on Me as it moves to any zone other than a player's hand or library. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new MeTheImmortalEffect())); + // You may cast Me from your graveyard by discarding two cards in addition to paying its other costs. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new MeTheImmortalCastEffect()) + .setIdentifier(MageIdentifier.MeTheImmortalAlternateCast)); + } + + private MeTheImmortal(final MeTheImmortal card) { + super(card); + } + + @Override + public MeTheImmortal copy() { + return new MeTheImmortal(this); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + boolean skullBriarEffectApplied = false; + if (event.getToZone() != Zone.HAND && event.getToZone() != Zone.LIBRARY) { + for (StaticAbility ability : getAbilities(game).getStaticAbilities(event.getFromZone())) { + for (Effect effect : ability.getEffects(game, EffectType.REPLACEMENT)) { + if (effect instanceof MeTheImmortalEffect && event.getAppliedEffects().contains(effect.getId())) { + skullBriarEffectApplied = true; + } + } + } + } + Counters copyFrom = null; + if (skullBriarEffectApplied) { + if (event.getTarget() != null && event.getFromZone() == Zone.BATTLEFIELD) { + copyFrom = event.getTarget().getCounters(game).copy(); + } else { + copyFrom = this.getCounters(game).copy(); + } + } + super.updateZoneChangeCounter(game, event); + Counters copyTo = null; + if (event.getTarget() != null && event.getToZone() == Zone.BATTLEFIELD) { + if (event.getFromZone() != Zone.BATTLEFIELD) { + copyTo = event.getTarget().getCounters(game); + } + } else { + copyTo = this.getCounters(game); + } + if (copyTo != null && copyFrom != null) { + for (Counter counter : copyFrom.values()) { + copyTo.addCounter(counter); + } + } + } +} + +class MeTheImmortalEffect extends ReplacementEffectImpl { + + MeTheImmortalEffect() { + super(Duration.EndOfGame, Outcome.Benefit); + staticText = "Counters remain on {this} as it moves to any zone other than a player's hand or library."; + } + + private MeTheImmortalEffect(final MeTheImmortalEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getSourceId().equals(event.getTargetId()); + } + + @Override + public MeTheImmortalEffect copy() { + return new MeTheImmortalEffect(this); + } +} + +class MeTheImmortalCastEffect extends AsThoughEffectImpl { + + MeTheImmortalCastEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + this.staticText = "you may cast {this} from your graveyard " + + "by discarding two cards in addition to paying its other costs"; + } + + private MeTheImmortalCastEffect(final MeTheImmortalCastEffect effect) { + super(effect); + } + + @Override + public MeTheImmortalCastEffect copy() { + return new MeTheImmortalCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.getSourceId().equals(objectId) + || !source.isControlledBy(affectedControllerId) + || game.getState().getZone(objectId) != Zone.GRAVEYARD) { + return false; + } + Player controller = game.getPlayer(affectedControllerId); + if (controller == null) { + return false; + } + Costs costs = new CostsImpl<>(); + costs.add(new DiscardTargetCost(new TargetCardInHand(2, StaticFilters.FILTER_CARD_CARDS))); + controller.setCastSourceIdWithAlternateMana( + objectId, new ManaCostsImpl<>("{2}{G}{U}{R}"), costs, + MageIdentifier.MeTheImmortalAlternateCast + ); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MeasureOfWickedness.java b/Mage.Sets/src/mage/cards/m/MeasureOfWickedness.java index 0e9e8dd808e..d175328f21d 100644 --- a/Mage.Sets/src/mage/cards/m/MeasureOfWickedness.java +++ b/Mage.Sets/src/mage/cards/m/MeasureOfWickedness.java @@ -1,30 +1,23 @@ - package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author LevelX2 @@ -50,7 +43,7 @@ public final class MeasureOfWickedness extends CardImpl { // Whenever another card is put into your graveyard from anywhere, target opponent gains control of Measure of Wickedness. ability = new PutCardIntoGraveFromAnywhereAllTriggeredAbility( - new MeasureOfWickednessControlSourceEffect(), false, filter, TargetController.YOU); + new TargetPlayerGainControlSourceEffect(), false, filter, TargetController.YOU); ability.addTarget(new TargetOpponent()); this.addAbility(ability); @@ -65,33 +58,3 @@ public final class MeasureOfWickedness extends CardImpl { return new MeasureOfWickedness(this); } } - -class MeasureOfWickednessControlSourceEffect extends ContinuousEffectImpl { - - MeasureOfWickednessControlSourceEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "target opponent gains control of {this}"; - } - - private MeasureOfWickednessControlSourceEffect(final MeasureOfWickednessControlSourceEffect effect) { - super(effect); - } - - @Override - public MeasureOfWickednessControlSourceEffect copy() { - return new MeasureOfWickednessControlSourceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player targetOpponent = game.getPlayer(source.getFirstTarget()); - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null && targetOpponent != null) { - permanent.changeControllerId(targetOpponent.getId(), game, source); - } else { - // no valid target exists, effect can be discarded - discard(); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/m/Meglonoth.java b/Mage.Sets/src/mage/cards/m/Meglonoth.java index 8d352e23847..be81e58eed7 100644 --- a/Mage.Sets/src/mage/cards/m/Meglonoth.java +++ b/Mage.Sets/src/mage/cards/m/Meglonoth.java @@ -68,7 +68,7 @@ class MeglonothEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent meglonoth = game.getPermanent(source.getSourceId()); - Permanent blocked = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent blocked = game.getPermanent(getTargetPointer().getFirst(game, source)); if (blocked != null && meglonoth != null) { game.getPlayer(blocked.getControllerId()).damage(meglonoth.getPower().getValue(), source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java b/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java new file mode 100644 index 00000000000..096a59ff44f --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java @@ -0,0 +1,126 @@ +package mage.cards.m; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.Card; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author DominionSpy + */ +public final class MelekReforgedResearcher extends CardImpl { + + private static final FilterCard filter = new FilterInstantOrSorceryCard("the first instant or sorcery spell"); + + static { + filter.add(MelekReforgedResearcherPredicate.instance); + } + + public MelekReforgedResearcher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WEIRD); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Melek, Reforged Researcher's power and toughness are each equal to twice the number of instant and sorcery cards in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerToughnessSourceEffect( + new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY, 2)) + .setText("{this}'s power and toughness are each equal to twice the number of instant and sorcery cards in your graveyard"))); + + // The first instant or sorcery spell you cast each turn costs {3} less to cast. + Effect effect = new SpellsCostReductionControllerEffect(filter, 3); + effect.setText("The first instant or sorcery spell you cast each turn costs {3} less to cast"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect), + new MelekReforgedResearcherWatcher()); + } + + private MelekReforgedResearcher(final MelekReforgedResearcher card) { + super(card); + } + + @Override + public MelekReforgedResearcher copy() { + return new MelekReforgedResearcher(this); + } +} + +enum MelekReforgedResearcherPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() instanceof Card && + ((Card) input.getObject()).isInstantOrSorcery(game)) { + MelekReforgedResearcherWatcher watcher = game.getState().getWatcher(MelekReforgedResearcherWatcher.class); + return watcher != null && + watcher.getInstantOrSorcerySpellsCastThisTurn(input.getPlayerId()) == 0; + } + return false; + } + + @Override + public String toString() { + return "The first instant or sorcery spell you cast each turn"; + } +} + +class MelekReforgedResearcherWatcher extends Watcher { + + private final Map playerInstantOrSorcerySpells = new HashMap<>(); + + MelekReforgedResearcherWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch (GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null || !spell.isInstantOrSorcery(game)) { + return; + } + playerInstantOrSorcerySpells.put(event.getPlayerId(), + getInstantOrSorcerySpellsCastThisTurn(event.getPlayerId()) + 1); + } + + @Override + public void reset() { + playerInstantOrSorcerySpells.clear(); + super.reset(); + } + + public int getInstantOrSorcerySpellsCastThisTurn(UUID playerId) { + return playerInstantOrSorcerySpells.getOrDefault(playerId, 0); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeletisCharlatan.java b/Mage.Sets/src/mage/cards/m/MeletisCharlatan.java index 2e752379c6b..09c3865f608 100644 --- a/Mage.Sets/src/mage/cards/m/MeletisCharlatan.java +++ b/Mage.Sets/src/mage/cards/m/MeletisCharlatan.java @@ -65,7 +65,7 @@ class MeletisCharlatanCopyTargetSpellEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { spell.createCopyOnStack(game, source, spell.getControllerId(), true); return true; diff --git a/Mage.Sets/src/mage/cards/m/Melting.java b/Mage.Sets/src/mage/cards/m/Melting.java index 18ae08341a6..80ca0686085 100644 --- a/Mage.Sets/src/mage/cards/m/Melting.java +++ b/Mage.Sets/src/mage/cards/m/Melting.java @@ -6,7 +6,7 @@ import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterLandPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -36,9 +36,7 @@ public final class Melting extends CardImpl { class MeltingEffect extends ContinuousEffectImpl { - private static final FilterLandPermanent filter = new FilterLandPermanent(); - - public MeltingEffect() { + MeltingEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); this.staticText = "All lands are no longer snow"; } @@ -54,7 +52,7 @@ class MeltingEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), source, game)) { permanent.removeSuperType(game, SuperType.SNOW); } return true; diff --git a/Mage.Sets/src/mage/cards/m/Memoricide.java b/Mage.Sets/src/mage/cards/m/Memoricide.java index ba198e1cfdc..4e6b0fe2c9e 100644 --- a/Mage.Sets/src/mage/cards/m/Memoricide.java +++ b/Mage.Sets/src/mage/cards/m/Memoricide.java @@ -54,7 +54,7 @@ class MemoricideEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExi if (cardName == null) { return false; } - return super.applySearchAndExile(game, source, cardName, targetPointer.getFirst(game, source)); + return super.applySearchAndExile(game, source, cardName, getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/m/MerchantOfTruth.java b/Mage.Sets/src/mage/cards/m/MerchantOfTruth.java new file mode 100644 index 00000000000..f99d8a5d0de --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MerchantOfTruth.java @@ -0,0 +1,57 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.ExaltedAbility; +import mage.abilities.keyword.FlyingAbility; +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 java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class MerchantOfTruth extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "Clues"); + + public MerchantOfTruth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}"); + this.subtype.add(SubType.ANGEL, SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever a nontoken creature you control dies, investigate. + this.addAbility(new DiesCreatureTriggeredAbility( + new InvestigateEffect(false), false, StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN + )); + + // Clues you control have exalted. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new ExaltedAbility(), Duration.WhileOnBattlefield, filter + ).setText("Clues you control have exalted. (Whenever a creature you control attacks alone, that creature " + + "gets +1/+1 until end of turn for each instance of exalted among permanents you control.)" + ))); + } + + private MerchantOfTruth(final MerchantOfTruth card) { + super(card); + } + + @Override + public MerchantOfTruth copy() { + return new MerchantOfTruth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MercurialPretender.java b/Mage.Sets/src/mage/cards/m/MercurialPretender.java index 561928355c2..f199007e8f0 100644 --- a/Mage.Sets/src/mage/cards/m/MercurialPretender.java +++ b/Mage.Sets/src/mage/cards/m/MercurialPretender.java @@ -12,7 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.util.functions.AbilityCopyApplier; import java.util.UUID; @@ -34,7 +34,7 @@ public final class MercurialPretender extends CardImpl { // You may have Mercurial Pretender enter the battlefield as a copy of any creature you control, // except it has "{2}{U}{U}: Return this creature to its owner's hand." - Effect effect = new CopyPermanentEffect(new FilterControlledCreaturePermanent(), + Effect effect = new CopyPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE, new AbilityCopyApplier(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(true), new ManaCostsImpl<>("{2}{U}{U}")))); effect.setText(effectText); this.addAbility(new EntersBattlefieldAbility(effect, true)); diff --git a/Mage.Sets/src/mage/cards/m/MerenOfClanNelToth.java b/Mage.Sets/src/mage/cards/m/MerenOfClanNelToth.java index 3d01b1c5047..12ac00e7406 100644 --- a/Mage.Sets/src/mage/cards/m/MerenOfClanNelToth.java +++ b/Mage.Sets/src/mage/cards/m/MerenOfClanNelToth.java @@ -74,7 +74,7 @@ class MerenOfClanNelTothEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (player == null || card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/m/MerfolkSpy.java b/Mage.Sets/src/mage/cards/m/MerfolkSpy.java index ce493c02a8a..8d42e7f6f2e 100644 --- a/Mage.Sets/src/mage/cards/m/MerfolkSpy.java +++ b/Mage.Sets/src/mage/cards/m/MerfolkSpy.java @@ -60,7 +60,7 @@ class MerfolkSpyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null && !player.getHand().isEmpty()) { Cards revealed = new CardsImpl(); revealed.add(player.getHand().getRandom(game)); diff --git a/Mage.Sets/src/mage/cards/m/MesmerizingBenthid.java b/Mage.Sets/src/mage/cards/m/MesmerizingBenthid.java index 330da204905..7ce23a1f690 100644 --- a/Mage.Sets/src/mage/cards/m/MesmerizingBenthid.java +++ b/Mage.Sets/src/mage/cards/m/MesmerizingBenthid.java @@ -13,7 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.MesmerizingBenthidToken; import java.util.UUID; @@ -23,7 +23,7 @@ import java.util.UUID; */ public final class MesmerizingBenthid extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.ILLUSION); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ILLUSION); public MesmerizingBenthid(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); diff --git a/Mage.Sets/src/mage/cards/m/MilitiasPride.java b/Mage.Sets/src/mage/cards/m/MilitiasPride.java index a96fb40d0da..b59a4a23440 100644 --- a/Mage.Sets/src/mage/cards/m/MilitiasPride.java +++ b/Mage.Sets/src/mage/cards/m/MilitiasPride.java @@ -1,6 +1,6 @@ package mage.cards.m; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DoIfCostPaid; @@ -8,12 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.game.permanent.token.KithkinSoldierToken; import java.util.UUID; @@ -23,12 +19,21 @@ import java.util.UUID; */ public final class MilitiasPride extends CardImpl { + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("nontoken creature you control"); + + static { + filter.add(TokenPredicate.FALSE); + } + public MilitiasPride(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.TRIBAL, CardType.ENCHANTMENT}, "{1}{W}"); this.subtype.add(SubType.KITHKIN); - // Whenever a creature you control attacks, you may pay {W}. If you do create a 1/1 white Kithkin Soldier creature token in play tapped and attacking - this.addAbility(new MilitiasPrideTriggerAbility()); + // Whenever a nontoken creature you control attacks, you may pay {W}. If you do, create a 1/1 white Kithkin Soldier creature token that’s tapped and attacking. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility( + new DoIfCostPaid(new CreateTokenEffect(new KithkinSoldierToken(), 1, true, true), new ManaCostsImpl<>("{W}")), + false, filter + )); } @@ -41,37 +46,3 @@ public final class MilitiasPride extends CardImpl { return new MilitiasPride(this); } } - -class MilitiasPrideTriggerAbility extends TriggeredAbilityImpl { - - public MilitiasPrideTriggerAbility() { - super(Zone.BATTLEFIELD, new DoIfCostPaid(new CreateTokenEffect(new KithkinSoldierToken(), 1, true, true), new ManaCostsImpl<>("{W}"))); - } - - private MilitiasPrideTriggerAbility(final MilitiasPrideTriggerAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - filter.add(TokenPredicate.FALSE); - Permanent permanent = game.getPermanent(event.getSourceId()); - return permanent != null && filter.match(permanent, controllerId, this, game); - } - - @Override - public String getRule() { - return "Whenever a nontoken creature you control attacks, you may pay {W}. If you do, create a 1/1 white Kithkin Soldier creature token that's tapped and attacking."; - } - - @Override - public MilitiasPrideTriggerAbility copy() { - return new MilitiasPrideTriggerAbility(this); - } -} diff --git a/Mage.Sets/src/mage/cards/m/MinasMorgulDarkFortress.java b/Mage.Sets/src/mage/cards/m/MinasMorgulDarkFortress.java index 3ef0a3dae90..406fbb0d0fe 100644 --- a/Mage.Sets/src/mage/cards/m/MinasMorgulDarkFortress.java +++ b/Mage.Sets/src/mage/cards/m/MinasMorgulDarkFortress.java @@ -74,7 +74,7 @@ class MinasMorgulEffect extends AddCardSubTypeTargetEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (creature == null || creature.getCounters(game).getCount(CounterType.SHADOW) < 1) { discard(); return false; diff --git a/Mage.Sets/src/mage/cards/m/MindWhip.java b/Mage.Sets/src/mage/cards/m/MindWhip.java index 0059fd33bf6..e56e53a8498 100644 --- a/Mage.Sets/src/mage/cards/m/MindWhip.java +++ b/Mage.Sets/src/mage/cards/m/MindWhip.java @@ -75,7 +75,7 @@ class MindWhipEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controllerOfEnchantedCreature = game.getPlayer(targetPointer.getFirst(game, source)); + Player controllerOfEnchantedCreature = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent mindWhip = game.getPermanent(source.getSourceId()); if (controllerOfEnchantedCreature != null && mindWhip != null) { diff --git a/Mage.Sets/src/mage/cards/m/Mindblaze.java b/Mage.Sets/src/mage/cards/m/Mindblaze.java index cdcf70ffa02..f3b45ffb051 100644 --- a/Mage.Sets/src/mage/cards/m/Mindblaze.java +++ b/Mage.Sets/src/mage/cards/m/Mindblaze.java @@ -64,7 +64,7 @@ class MindblazeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player playerControls = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (player == null || playerControls == null || sourceObject == null) { diff --git a/Mage.Sets/src/mage/cards/m/Mindsparker.java b/Mage.Sets/src/mage/cards/m/Mindsparker.java index e21a1485fdf..3876e8ff432 100644 --- a/Mage.Sets/src/mage/cards/m/Mindsparker.java +++ b/Mage.Sets/src/mage/cards/m/Mindsparker.java @@ -76,7 +76,7 @@ class MindsparkerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(2, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/m/MinotaurIllusionist.java b/Mage.Sets/src/mage/cards/m/MinotaurIllusionist.java index dc65f3d3ba7..7714e910eee 100644 --- a/Mage.Sets/src/mage/cards/m/MinotaurIllusionist.java +++ b/Mage.Sets/src/mage/cards/m/MinotaurIllusionist.java @@ -39,7 +39,7 @@ public final class MinotaurIllusionist extends CardImpl { Duration.EndOfTurn), new ManaCostsImpl<>("{1}{U}"))); // {R}, Sacrifice Minotaur Illusionist: Minotaur Illusionist deals damage equal to its power to target creature. Effect effect = new DamageTargetEffect(new SourcePermanentPowerCount()); - effect.setText("{this} deals damage equal to its power to target creature."); + effect.setText("it deals damage equal to its power to target creature."); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{R}")); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/m/MirkoObsessiveTheorist.java b/Mage.Sets/src/mage/cards/m/MirkoObsessiveTheorist.java new file mode 100644 index 00000000000..dd325ba83ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MirkoObsessiveTheorist.java @@ -0,0 +1,80 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; +import mage.abilities.common.SurveilTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class MirkoObsessiveTheorist extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("creature with power less than this creature's"); + + static { + filter.add(MirkoObsessiveTheoristPredicate.instance); + } + + public MirkoObsessiveTheorist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE, SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever you surveil, put a +1/+1 counter on Mirko, Obsessive Theorist. + this.addAbility(new SurveilTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + + // At the beginning of your end step, you may return target creature card with power less than Mirko's from your graveyard to the battlefield with a finality counter on it. + Ability ability = new BeginningOfYourEndStepTriggeredAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) + .setText("you may return target creature card with power less than {this}'s from your graveyard to the " + + "battlefield with a finality counter on it. (If it would die, exile it instead.)"),true + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private MirkoObsessiveTheorist(final MirkoObsessiveTheorist card) { + super(card); + } + + @Override + public MirkoObsessiveTheorist copy() { + return new MirkoObsessiveTheorist(this); + } +} + +enum MirkoObsessiveTheoristPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent sourcePermanent = input.getSource().getSourcePermanentOrLKI(game); + return sourcePermanent != null && input.getObject().getPower().getValue() <= sourcePermanent.getPower().getValue(); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MirkwoodElk.java b/Mage.Sets/src/mage/cards/m/MirkwoodElk.java index f6ccc5fa324..5fdb4288592 100644 --- a/Mage.Sets/src/mage/cards/m/MirkwoodElk.java +++ b/Mage.Sets/src/mage/cards/m/MirkwoodElk.java @@ -75,7 +75,7 @@ class MirkwoodElkEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card targetElf = game.getCard(targetPointer.getFirst(game, source)); + Card targetElf = game.getCard(getTargetPointer().getFirst(game, source)); if (targetElf != null) { controller.moveCards(targetElf, Zone.HAND, source, game); controller.gainLife(targetElf.getPower().getValue(), game, source); diff --git a/Mage.Sets/src/mage/cards/m/MirrorMarch.java b/Mage.Sets/src/mage/cards/m/MirrorMarch.java index fc6029cad23..dcd8ea0d188 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorMarch.java +++ b/Mage.Sets/src/mage/cards/m/MirrorMarch.java @@ -77,7 +77,7 @@ class MirrorMarchEffect extends OneShotEffect { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(player.getId(), null, true, counter); effect.setUseLKI(true); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); effect.exileTokensCreatedAtNextEndStep(game, source); } diff --git a/Mage.Sets/src/mage/cards/m/MisterGutsy.java b/Mage.Sets/src/mage/cards/m/MisterGutsy.java new file mode 100644 index 00000000000..7149828e46f --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MisterGutsy.java @@ -0,0 +1,60 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.JunkToken; + +/** + * @author Cguy7777 + */ +public final class MisterGutsy extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an Aura or Equipment spell"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate())); + } + + public MisterGutsy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever you cast an Aura or Equipment spell, put a +1/+1 counter on Mister Gutsy. + this.addAbility(new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter, false)); + + // When Mister Gutsy dies, create X Junk tokens, where X is the number of +1/+1 counters on it. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new JunkToken(), new CountersSourceCount(CounterType.P1P1)) + .setText("create X Junk tokens, where X is the number of +1/+1 counters on it. " + + "(They're artifacts with \"{T}, Sacrifice this artifact: Exile the top card of your library. " + + "You may play that card this turn. Activate only as a sorcery.\")"))); + } + + private MisterGutsy(final MisterGutsy card) { + super(card); + } + + @Override + public MisterGutsy copy() { + return new MisterGutsy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MobRule.java b/Mage.Sets/src/mage/cards/m/MobRule.java index 85a3886064e..44e9b7f5715 100644 --- a/Mage.Sets/src/mage/cards/m/MobRule.java +++ b/Mage.Sets/src/mage/cards/m/MobRule.java @@ -1,23 +1,19 @@ package mage.cards.m; -import java.util.UUID; import mage.abilities.Mode; -import mage.abilities.effects.common.UntapAllEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; -import mage.abilities.effects.common.continuous.GainControlAllEffect; -import mage.abilities.keyword.HasteAbility; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; -import mage.constants.Duration; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; +import java.util.UUID; + /** - * - * @author awjackson + * @author xenohedron */ public final class MobRule extends CardImpl { @@ -34,26 +30,10 @@ public final class MobRule extends CardImpl { // Choose one // Gain control of all creatures with power 4 or greater until end of turn. Untap those creatures. They gain haste until end of turn. - this.getSpellAbility().addEffect(new GainControlAllEffect(Duration.EndOfTurn, filter4orMore)); - this.getSpellAbility().addEffect(new UntapAllEffect(filter4orMore).setText("untap those creatures")); - this.getSpellAbility().addEffect(new GainAbilityAllEffect( - HasteAbility.getInstance(), - Duration.EndOfTurn, - filter4orMore, - "they gain haste until end of turn" - )); + this.getSpellAbility().addEffect(new GainControlAllUntapGainHasteEffect(filter4orMore).withTextOptions("those creatures")); // Gain control of all creatures with power 3 or less until end of turn. Untap those creatures. They gain haste until end of turn. - Mode mode = new Mode(new GainControlAllEffect(Duration.EndOfTurn, filter3orLess)); - mode.addEffect(new UntapAllEffect(filter3orLess).setText("untap those creatures")); - mode.addEffect(new GainAbilityAllEffect( - HasteAbility.getInstance(), - Duration.EndOfTurn, - filter3orLess, - "they gain haste until end of turn" - )); - - this.getSpellAbility().addMode(mode); + this.getSpellAbility().addMode(new Mode(new GainControlAllUntapGainHasteEffect(filter3orLess).withTextOptions("those creatures"))); } private MobRule(final MobRule card) { diff --git a/Mage.Sets/src/mage/cards/m/Mobilize.java b/Mage.Sets/src/mage/cards/m/Mobilize.java index f1e4032a154..0e0d5c74381 100644 --- a/Mage.Sets/src/mage/cards/m/Mobilize.java +++ b/Mage.Sets/src/mage/cards/m/Mobilize.java @@ -1,4 +1,3 @@ - package mage.cards.m; import java.util.UUID; @@ -6,7 +5,7 @@ import mage.abilities.effects.common.UntapAllControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -20,7 +19,7 @@ public final class Mobilize extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{G}"); // Untap all creatures you control. - this.getSpellAbility().addEffect(new UntapAllControllerEffect(new FilterControlledCreaturePermanent(), rule)); + this.getSpellAbility().addEffect(new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURE, rule)); } private Mobilize(final Mobilize card) { diff --git a/Mage.Sets/src/mage/cards/m/MoggInfestation.java b/Mage.Sets/src/mage/cards/m/MoggInfestation.java index 61451d37151..89599024ecf 100644 --- a/Mage.Sets/src/mage/cards/m/MoggInfestation.java +++ b/Mage.Sets/src/mage/cards/m/MoggInfestation.java @@ -85,7 +85,7 @@ class MoggInfestationEffect extends OneShotEffect { || (game.getLastKnownInformation(uuid, Zone.BATTLEFIELD) instanceof PermanentToken && !game.getBattlefield().containsPermanent(uuid))) { Effect effect = new CreateTokenTargetEffect(new GoblinToken(), 2); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/m/MoltenEchoes.java b/Mage.Sets/src/mage/cards/m/MoltenEchoes.java index 6f414956863..0c7047bc9ff 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenEchoes.java +++ b/Mage.Sets/src/mage/cards/m/MoltenEchoes.java @@ -78,7 +78,7 @@ class MoltenEchoesEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, null, true); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect.apply(game, source)) { for (Permanent tokenPermanent : effect.getAddedPermanents()) { ExileTargetEffect exileEffect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/m/MorbidBloom.java b/Mage.Sets/src/mage/cards/m/MorbidBloom.java index be4780fd4ea..b7f81783b49 100644 --- a/Mage.Sets/src/mage/cards/m/MorbidBloom.java +++ b/Mage.Sets/src/mage/cards/m/MorbidBloom.java @@ -9,6 +9,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.token.SaprolingToken; @@ -22,14 +23,12 @@ import java.util.UUID; */ public final class MorbidBloom extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public MorbidBloom(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{G}"); // Exile target creature card from a graveyard, then create X 1/1 green Saproling creature tokens, where X is the exiled card's toughness. this.getSpellAbility().addEffect(new MorbidBloomEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); } private MorbidBloom(final MorbidBloom card) { diff --git a/Mage.Sets/src/mage/cards/m/MournersShield.java b/Mage.Sets/src/mage/cards/m/MournersShield.java index e6a7e6bf7bd..20902ca4436 100644 --- a/Mage.Sets/src/mage/cards/m/MournersShield.java +++ b/Mage.Sets/src/mage/cards/m/MournersShield.java @@ -123,6 +123,7 @@ class MournersShieldEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); ObjectColor colorsAmongImprinted = new ObjectColor(); Permanent sourceObject = game.getPermanent(source.getSourceId()); ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId())); diff --git a/Mage.Sets/src/mage/cards/m/Mudslide.java b/Mage.Sets/src/mage/cards/m/Mudslide.java index 26fe578a3ac..2f09d1d3ea8 100644 --- a/Mage.Sets/src/mage/cards/m/Mudslide.java +++ b/Mage.Sets/src/mage/cards/m/Mudslide.java @@ -81,7 +81,7 @@ class MudslideEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && sourcePermanent != null) { int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); diff --git a/Mage.Sets/src/mage/cards/m/MundasVanguard.java b/Mage.Sets/src/mage/cards/m/MundasVanguard.java index e2975f71c72..20cd35d557c 100644 --- a/Mage.Sets/src/mage/cards/m/MundasVanguard.java +++ b/Mage.Sets/src/mage/cards/m/MundasVanguard.java @@ -1,24 +1,16 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -26,13 +18,6 @@ import mage.target.common.TargetControlledPermanent; */ public final class MundasVanguard extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public MundasVanguard(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}"); this.subtype.add(SubType.KOR); @@ -42,11 +27,7 @@ public final class MundasVanguard extends CardImpl { this.toughness = new MageInt(3); // Cohort — {T}, Tap an untapped Ally you control: Put a +1/+1 counter on each creature you control. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()), - new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); - ability.setAbilityWord(AbilityWord.COHORT); - this.addAbility(ability); + this.addAbility(new CohortAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE))); } private MundasVanguard(final MundasVanguard card) { diff --git a/Mage.Sets/src/mage/cards/m/MurasaPyromancer.java b/Mage.Sets/src/mage/cards/m/MurasaPyromancer.java index 953c54f6b3e..93924c19b79 100644 --- a/Mage.Sets/src/mage/cards/m/MurasaPyromancer.java +++ b/Mage.Sets/src/mage/cards/m/MurasaPyromancer.java @@ -1,4 +1,3 @@ - package mage.cards.m; import java.util.UUID; @@ -11,8 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -21,12 +19,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class MurasaPyromancer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "Ally you control"); public MurasaPyromancer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{R}{R}"); diff --git a/Mage.Sets/src/mage/cards/m/MuseVessel.java b/Mage.Sets/src/mage/cards/m/MuseVessel.java index d83069d02f7..e05e43e9e39 100644 --- a/Mage.Sets/src/mage/cards/m/MuseVessel.java +++ b/Mage.Sets/src/mage/cards/m/MuseVessel.java @@ -71,7 +71,7 @@ class MuseVesselExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (sourceObject == null) { return false; diff --git a/Mage.Sets/src/mage/cards/m/MycosynthGolem.java b/Mage.Sets/src/mage/cards/m/MycosynthGolem.java index b663c77f676..233a5a05250 100644 --- a/Mage.Sets/src/mage/cards/m/MycosynthGolem.java +++ b/Mage.Sets/src/mage/cards/m/MycosynthGolem.java @@ -9,7 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import java.util.UUID; @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class MycosynthGolem extends CardImpl { - private static final FilterCard filter = new FilterCard("Artifact creature spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("Artifact creature spells you cast"); static { filter.add(CardType.ARTIFACT.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java b/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java index b250f75505e..b418d537282 100644 --- a/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java +++ b/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java @@ -61,9 +61,9 @@ public class MyojinOfGrimBetrayal extends CardImpl { ability.addWatcher(new CardsPutIntoGraveyardWatcher()); this.addAbility(ability); } - + private MyojinOfGrimBetrayal(final MyojinOfGrimBetrayal card) { super(card); } - + @Override public MyojinOfGrimBetrayal copy() {return new MyojinOfGrimBetrayal(this); } } @@ -92,7 +92,7 @@ class MyojinOfGrimBetrayalEffect extends OneShotEffect { return false; } - Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game)); + Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromAnywhere(game)); cards.removeIf(uuid -> !game.getCard(uuid).isCreature(game)); return controller.moveCards(cards, Zone.BATTLEFIELD, source, game); @@ -100,4 +100,4 @@ class MyojinOfGrimBetrayalEffect extends OneShotEffect { @Override public MyojinOfGrimBetrayalEffect copy() { return new MyojinOfGrimBetrayalEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/m/MyrBattlesphere.java b/Mage.Sets/src/mage/cards/m/MyrBattlesphere.java index f2449332505..7986dbf9689 100644 --- a/Mage.Sets/src/mage/cards/m/MyrBattlesphere.java +++ b/Mage.Sets/src/mage/cards/m/MyrBattlesphere.java @@ -10,7 +10,7 @@ import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.events.GameEvent; @@ -95,7 +95,7 @@ class MyrBattlesphereTriggeredAbility extends TriggeredAbilityImpl { class MyrBattlesphereEffect extends OneShotEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Myr you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Myr you control"); static { filter.add(TappedPredicate.UNTAPPED); @@ -142,7 +142,7 @@ class MyrBattlesphereEffect extends OneShotEffect { // boost effect game.addEffect(new BoostSourceEffect(tappedAmount, 0, Duration.EndOfTurn), source); // damage to defender - return game.damagePlayerOrPermanent(targetPointer.getFirst(game, source), tappedAmount, myr.getId(), source, game, false, true) > 0; + return game.damagePlayerOrPermanent(getTargetPointer().getFirst(game, source), tappedAmount, myr.getId(), source, game, false, true) > 0; } return true; } diff --git a/Mage.Sets/src/mage/cards/m/MysticForge.java b/Mage.Sets/src/mage/cards/m/MysticForge.java index 09ad165fa8c..158399d614a 100644 --- a/Mage.Sets/src/mage/cards/m/MysticForge.java +++ b/Mage.Sets/src/mage/cards/m/MysticForge.java @@ -5,21 +5,17 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorlessPredicate; -import mage.game.Game; -import mage.players.Player; import java.util.UUID; @@ -47,7 +43,7 @@ public final class MysticForge extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // {T}, Pay 1 life: Exile the top card of your library. - Ability ability = new SimpleActivatedAbility(new MysticForgeExileEffect(), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new ExileCardsFromTopOfLibraryControllerEffect(1), new TapSourceCost()); ability.addCost(new PayLifeCost(1)); this.addAbility(ability); } @@ -61,29 +57,3 @@ public final class MysticForge extends CardImpl { return new MysticForge(this); } } - -class MysticForgeExileEffect extends OneShotEffect { - - MysticForgeExileEffect() { - super(Outcome.Benefit); - staticText = "exile the top card of your library"; - } - - private MysticForgeExileEffect(final MysticForgeExileEffect effect) { - super(effect); - } - - @Override - public MysticForgeExileEffect copy() { - return new MysticForgeExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - return controller.moveCards(controller.getLibrary().getFromTop(game), Zone.EXILED, source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/m/MysticGenesis.java b/Mage.Sets/src/mage/cards/m/MysticGenesis.java index 49947d2466e..0b053494df7 100644 --- a/Mage.Sets/src/mage/cards/m/MysticGenesis.java +++ b/Mage.Sets/src/mage/cards/m/MysticGenesis.java @@ -57,7 +57,7 @@ class MysticGenesisEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackObject != null) { game.getStack().counter(source.getFirstTarget(), source, game); return new CreateTokenEffect(new OozeToken(stackObject.getManaValue(), stackObject.getManaValue())).apply(game, source); diff --git a/Mage.Sets/src/mage/cards/m/MysticRemora.java b/Mage.Sets/src/mage/cards/m/MysticRemora.java index a9924b22808..ff00eb435d4 100644 --- a/Mage.Sets/src/mage/cards/m/MysticRemora.java +++ b/Mage.Sets/src/mage/cards/m/MysticRemora.java @@ -113,7 +113,7 @@ class MysticRemoraEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (controller != null && opponent != null && sourceObject != null) { if (controller.chooseUse(Outcome.DrawCard, "Draw a card (" + sourceObject.getLogName() + ')', source, game)) { diff --git a/Mage.Sets/src/mage/cards/m/MythosOfVadrok.java b/Mage.Sets/src/mage/cards/m/MythosOfVadrok.java index a18cd254903..fe65693100a 100644 --- a/Mage.Sets/src/mage/cards/m/MythosOfVadrok.java +++ b/Mage.Sets/src/mage/cards/m/MythosOfVadrok.java @@ -97,7 +97,7 @@ class MythosOfVadrokRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/n/NacatlWarPride.java b/Mage.Sets/src/mage/cards/n/NacatlWarPride.java index ab4ce40056d..b9b9b59e0a0 100644 --- a/Mage.Sets/src/mage/cards/n/NacatlWarPride.java +++ b/Mage.Sets/src/mage/cards/n/NacatlWarPride.java @@ -108,9 +108,9 @@ class NacatlWarPrideEffect extends OneShotEffect { copies.addAll(effect.getAddedPermanents()); if (!copies.isEmpty()) { - FixedTargets fixedTargets = new FixedTargets(copies, game); + FixedTargets blueprintTarget = new FixedTargets(copies, game); ExileTargetEffect exileEffect = new ExileTargetEffect(); - exileEffect.setTargetPointer(fixedTargets).setText("exile the tokens"); + exileEffect.setTargetPointer(blueprintTarget.copy()).setText("exile the tokens"); game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect), source); return true; } diff --git a/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java b/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java index bf7672255bd..db6121a9315 100644 --- a/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java +++ b/Mage.Sets/src/mage/cards/n/NameStickerGoblin.java @@ -63,11 +63,14 @@ public final class NameStickerGoblin extends CardImpl { } class NameStickerGoblinTriggeredAbility extends TriggeredAbilityImpl { + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); static{ filter.add(new NamePredicate("\"Name Sticker\" Goblin")); } + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.OR_LESS, 9); + NameStickerGoblinTriggeredAbility(Effect effect) { super(Zone.BATTLEFIELD, effect); setTriggerPhrase("When this creature enters the battlefield from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named \"Name Sticker\" Goblin, "); diff --git a/Mage.Sets/src/mage/cards/n/NarciFableSinger.java b/Mage.Sets/src/mage/cards/n/NarciFableSinger.java index b395cc96f69..a2b15173ce7 100644 --- a/Mage.Sets/src/mage/cards/n/NarciFableSinger.java +++ b/Mage.Sets/src/mage/cards/n/NarciFableSinger.java @@ -79,7 +79,7 @@ class NarciFableSingerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - FixedTarget fixedTarget = targetPointer.getFixedTarget(game, source); + FixedTarget fixedTarget = getTargetPointer().getFirstAsFixedTarget(game, source); if (fixedTarget == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/n/NaturalBalance.java b/Mage.Sets/src/mage/cards/n/NaturalBalance.java index fa9597db5b7..5a14f229b0c 100644 --- a/Mage.Sets/src/mage/cards/n/NaturalBalance.java +++ b/Mage.Sets/src/mage/cards/n/NaturalBalance.java @@ -43,66 +43,69 @@ public final class NaturalBalance extends CardImpl { return new NaturalBalance(this); } - class NaturalBalanceEffect extends OneShotEffect { +} - public NaturalBalanceEffect() { - super(Outcome.PutCardInPlay); - this.staticText = "Each player who controls six or more lands chooses five lands they control and sacrifices the rest. Each player who controls four or fewer lands may search their library for up to X basic land cards and put them onto the battlefield, where X is five minus the number of lands they control. Then each player who searched their library this way shuffles."; - } +class NaturalBalanceEffect extends OneShotEffect { - private NaturalBalanceEffect(final NaturalBalanceEffect effect) { - super(effect); - } + NaturalBalanceEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "Each player who controls six or more lands chooses five lands they control and sacrifices the rest. " + + "Each player who controls four or fewer lands may search their library for up to X basic land cards and put them onto the battlefield, " + + "where X is five minus the number of lands they control. Then each player who searched their library this way shuffles."; + } - @Override - public NaturalBalanceEffect copy() { - return new NaturalBalanceEffect(this); - } + private NaturalBalanceEffect(final NaturalBalanceEffect effect) { + super(effect); + } - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - PlayerList players = game.getState().getPlayersInRange(controller.getId(), game); - for (UUID playerId : players) { - Player player = game.getPlayer(playerId); - if (player != null) { - int landCount = game.getBattlefield().countAll(new FilterControlledLandPermanent(), player.getId(), game); - if (landCount > 5) { - // chooses five lands they control and sacrifices the rest - TargetControlledPermanent target = new TargetControlledPermanent(5, 5, new FilterControlledLandPermanent("lands to keep"), true); - if (target.choose(Outcome.Sacrifice, player.getId(), source.getSourceId(), source, game)) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(new FilterLandPermanent(), player.getId(), game)) { - if (!target.getTargets().contains(permanent.getId())) { - permanent.sacrifice(source, game); - } + @Override + public NaturalBalanceEffect copy() { + return new NaturalBalanceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + PlayerList players = game.getState().getPlayersInRange(controller.getId(), game); + for (UUID playerId : players) { + Player player = game.getPlayer(playerId); + if (player != null) { + int landCount = game.getBattlefield().countAll(new FilterControlledLandPermanent(), player.getId(), game); + if (landCount > 5) { + // chooses five lands they control and sacrifices the rest + TargetControlledPermanent target = new TargetControlledPermanent(5, 5, new FilterControlledLandPermanent("lands to keep"), true); + if (target.choose(Outcome.Sacrifice, player.getId(), source.getSourceId(), source, game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(new FilterLandPermanent(), player.getId(), game)) { + if (!target.getTargets().contains(permanent.getId())) { + permanent.sacrifice(source, game); } } } } } - List toShuffle = new ArrayList<>(); - for (UUID playerId : players) { - Player player = game.getPlayer(playerId); - if (player != null) { - int landCount = game.getBattlefield().countAll(new FilterControlledLandPermanent(), player.getId(), game); - int amount = 5 - landCount; - if (landCount < 5 && player.chooseUse(outcome, "Search your library for up to " + amount + " basic land cards and put them onto the battlefield?", source, game)) { - // Select lands and put them onto battlefield - TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, source, game)) { - player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game); - } - toShuffle.add(player); + } + List toShuffle = new ArrayList<>(); + for (UUID playerId : players) { + Player player = game.getPlayer(playerId); + if (player != null) { + int landCount = game.getBattlefield().countAll(new FilterControlledLandPermanent(), player.getId(), game); + int amount = 5 - landCount; + if (landCount < 5 && player.chooseUse(outcome, "Search your library for up to " + amount + " basic land cards and put them onto the battlefield?", source, game)) { + // Select lands and put them onto battlefield + TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_CARD_BASIC_LAND); + if (player.searchLibrary(target, source, game)) { + player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game); } + toShuffle.add(player); } } - for (Player player : toShuffle) { - player.shuffleLibrary(source, game); - } - return true; } - return false; + for (Player player : toShuffle) { + player.shuffleLibrary(source, game); + } + return true; } + return false; } } diff --git a/Mage.Sets/src/mage/cards/n/NavigationOrb.java b/Mage.Sets/src/mage/cards/n/NavigationOrb.java index a82ab57dad0..799b476eba3 100644 --- a/Mage.Sets/src/mage/cards/n/NavigationOrb.java +++ b/Mage.Sets/src/mage/cards/n/NavigationOrb.java @@ -5,15 +5,11 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; import mage.cards.*; import mage.constants.*; import mage.filter.FilterCard; -import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -23,11 +19,21 @@ import java.util.UUID; */ public final class NavigationOrb extends CardImpl { + private static final FilterCard filter = new FilterCard("basic land cards and/or Gate cards"); + + static { + filter.add(Predicates.or(Predicates.and( + CardType.LAND.getPredicate(), + SuperType.BASIC.getPredicate() + ), SubType.GATE.getPredicate())); + } + public NavigationOrb(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // {2}, {T}, Sacrifice Navigation Orb: Search your library for up to two basic land cards and/or Gate cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. - Ability ability = new SimpleActivatedAbility(new NavigationOrbEffect(), new GenericManaCost(2)); + Ability ability = new SimpleActivatedAbility(new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect( + new TargetCardInLibrary(0, 2, filter)), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); @@ -42,65 +48,3 @@ public final class NavigationOrb extends CardImpl { return new NavigationOrb(this); } } - -class NavigationOrbEffect extends OneShotEffect { - - private static final FilterCard filter = new FilterCard("basic land cards and/or Gate cards"); - - static { - filter.add(Predicates.or(Predicates.and( - CardType.LAND.getPredicate(), - SuperType.BASIC.getPredicate() - ), SubType.GATE.getPredicate())); - } - - NavigationOrbEffect() { - super(Outcome.Benefit); - staticText = "search your library for up to two basic land cards and/or Gate cards, reveal those cards, " + - "put one onto the battlefield tapped and the other into your hand, then shuffle"; - } - - private NavigationOrbEffect(final NavigationOrbEffect effect) { - super(effect); - } - - @Override - public NavigationOrbEffect copy() { - return new NavigationOrbEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - TargetCardInLibrary target = new TargetCardInLibrary(0, 2, filter); - player.searchLibrary(target, source, game); - Cards cards = new CardsImpl(); - target.getTargets() - .stream() - .map(uuid -> player.getLibrary().getCard(uuid, game)) - .forEach(cards::add); - player.revealCards(source, cards, game); - Card card; - switch (cards.size()) { - case 0: - player.shuffleLibrary(source, game); - return true; - case 1: - card = cards.getRandom(game); - break; - default: - TargetCard targetCard = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD); - targetCard.withChooseHint("To put onto the battlefield"); - player.choose(outcome, cards, targetCard, source, game); - card = cards.get(targetCard.getFirstTarget(), game); - } - cards.remove(card); - player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - player.moveCards(cards, Zone.HAND, source, game); - player.shuffleLibrary(source, game); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/n/Nebuchadnezzar.java b/Mage.Sets/src/mage/cards/n/Nebuchadnezzar.java index 56d5d8506fd..b9594554644 100644 --- a/Mage.Sets/src/mage/cards/n/Nebuchadnezzar.java +++ b/Mage.Sets/src/mage/cards/n/Nebuchadnezzar.java @@ -63,7 +63,7 @@ class NebuchadnezzarEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); if (opponent == null || sourceObject == null || cardName.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/n/Necroduality.java b/Mage.Sets/src/mage/cards/n/Necroduality.java index 55a0feee072..6a963f7aaa6 100644 --- a/Mage.Sets/src/mage/cards/n/Necroduality.java +++ b/Mage.Sets/src/mage/cards/n/Necroduality.java @@ -9,7 +9,7 @@ import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TokenPredicate; import java.util.UUID; @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class Necroduality extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.ZOMBIE, "a nontoken Zombie"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ZOMBIE, "a nontoken Zombie"); static { filter.add(TokenPredicate.FALSE); diff --git a/Mage.Sets/src/mage/cards/n/Necrogenesis.java b/Mage.Sets/src/mage/cards/n/Necrogenesis.java index a0df9bf6ac6..acc9be3f15e 100644 --- a/Mage.Sets/src/mage/cards/n/Necrogenesis.java +++ b/Mage.Sets/src/mage/cards/n/Necrogenesis.java @@ -11,7 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.game.permanent.token.SaprolingToken; import mage.target.common.TargetCardInGraveyard; @@ -26,7 +26,7 @@ public final class Necrogenesis extends CardImpl { // {2}: Exile target creature card from a graveyard. Create a 1/1 green Saproling creature token. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), new GenericManaCost(2)); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); ability.addEffect(new CreateTokenEffect(new SaprolingToken())); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/Necromancy.java b/Mage.Sets/src/mage/cards/n/Necromancy.java index 1460a6639cf..6a78315cbf5 100644 --- a/Mage.Sets/src/mage/cards/n/Necromancy.java +++ b/Mage.Sets/src/mage/cards/n/Necromancy.java @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -20,8 +21,6 @@ import java.util.UUID; */ public final class Necromancy extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("creature card from a graveyard"); - public Necromancy(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); @@ -35,7 +34,7 @@ public final class Necromancy extends CardImpl { // a graveyard onto the battlefield under your control and attach Necromancy to it. When Necromancy // leaves the battlefield, that creature's controller sacrifices it. Ability ability = new AnimateDeadTriggeredAbility(true); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/NecromanticSummons.java b/Mage.Sets/src/mage/cards/n/NecromanticSummons.java index f4a77106cc7..f747da43f03 100644 --- a/Mage.Sets/src/mage/cards/n/NecromanticSummons.java +++ b/Mage.Sets/src/mage/cards/n/NecromanticSummons.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.counters.CounterType; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; @@ -31,7 +31,7 @@ public final class NecromanticSummons extends CardImpl { this.getSpellAbility().addEffect(new NecromanticSummoningReplacementEffect());// has to be added before the moving effect // Put target creature card from a graveyard onto the battlefield under your control. this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, // that creature enters the battlefield with two additional +1/+1 counters on it. diff --git a/Mage.Sets/src/mage/cards/n/NeeraWildMage.java b/Mage.Sets/src/mage/cards/n/NeeraWildMage.java index 86228e28c61..be4bf5d1b04 100644 --- a/Mage.Sets/src/mage/cards/n/NeeraWildMage.java +++ b/Mage.Sets/src/mage/cards/n/NeeraWildMage.java @@ -65,7 +65,7 @@ class NeeraWildMageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/n/NemataPrimevalWarden.java b/Mage.Sets/src/mage/cards/n/NemataPrimevalWarden.java index 38c4fad173b..bf09226f059 100644 --- a/Mage.Sets/src/mage/cards/n/NemataPrimevalWarden.java +++ b/Mage.Sets/src/mage/cards/n/NemataPrimevalWarden.java @@ -16,11 +16,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -29,8 +29,8 @@ import java.util.UUID; */ public final class NemataPrimevalWarden extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Saproling"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("Saprolings"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Saproling"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("Saprolings"); static { filter.add(SubType.SAPROLING.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/n/NerdRage.java b/Mage.Sets/src/mage/cards/n/NerdRage.java new file mode 100644 index 00000000000..e1a8568bd12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NerdRage.java @@ -0,0 +1,72 @@ +package mage.cards.n; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CardsInHandCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.constants.*; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.effects.common.AttachEffect; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; + +/** + * @author Cguy7777 + */ +public final class NerdRage extends CardImpl { + + public NerdRage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Nerd Rage enters the battlefield, draw two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(2))); + + // Enchanted creature has "You have no maximum hand size" and "Whenever this creature attacks, + // if you have ten or more cards in hand, it gets +10/+10 until end of turn." + SimpleStaticAbility noMaxHandSizeAbility = new SimpleStaticAbility(new MaximumHandSizeControllerEffect( + Integer.MAX_VALUE, + Duration.WhileOnBattlefield, + MaximumHandSizeControllerEffect.HandSizeModification.SET)); + ConditionalInterveningIfTriggeredAbility attacksBoostAbility = new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new BoostSourceEffect(10, 10, Duration.EndOfTurn)), + new CardsInHandCondition(ComparisonType.MORE_THAN, 9), + "Whenever {this} attacks, if you have ten or more cards in hand, it gets +10/+10 until end of turn."); + + Effect gainNoMaxHandSizeEffect = new GainAbilityAttachedEffect(noMaxHandSizeAbility, AttachmentType.AURA) + .setText("Enchanted creature has \"You have no maximum hand size\""); + Effect gainAttacksBoostEffect = new GainAbilityAttachedEffect(attacksBoostAbility, AttachmentType.AURA) + .setText("\"Whenever this creature attacks, if you have ten or more cards in hand, it gets +10/+10 until end of turn.\""); + + Ability ability = new SimpleStaticAbility(gainNoMaxHandSizeEffect); + ability.addEffect(gainAttacksBoostEffect.concatBy("and")); + this.addAbility(ability); + } + + private NerdRage(final NerdRage card) { + super(card); + } + + @Override + public NerdRage copy() { + return new NerdRage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java b/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java index 24d33ce19bd..bc0801e0905 100644 --- a/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java +++ b/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java @@ -45,11 +45,11 @@ public final class NeurokTransmuter extends CardImpl { this.addAbility(becomeArtifactAbility); // {U}: Until end of turn, target artifact creature becomes blue and isn't an artifact. Effect blueEffect = new BecomesColorTargetEffect(ObjectColor.BLUE, Duration.EndOfTurn); - blueEffect.setText("Until end of turn, target artifact creature becomes blue and "); + blueEffect.setText("Until end of turn, target artifact creature becomes blue"); Ability becomeBlueAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, blueEffect, new ManaCostsImpl<>("{U}")); becomeBlueAbility.addTarget(new TargetCreaturePermanent(filter)); Effect loseArtifactEffect = new LoseArtifactTypeTargetEffect(Duration.EndOfTurn); - loseArtifactEffect.setText("isn't an artifact"); + loseArtifactEffect.setText("and isn't an artifact"); becomeBlueAbility.addEffect(loseArtifactEffect); this.addAbility(becomeBlueAbility); } diff --git a/Mage.Sets/src/mage/cards/n/NewBlood.java b/Mage.Sets/src/mage/cards/n/NewBlood.java index a9e47aad75e..19402099a53 100644 --- a/Mage.Sets/src/mage/cards/n/NewBlood.java +++ b/Mage.Sets/src/mage/cards/n/NewBlood.java @@ -110,6 +110,8 @@ class ChangeCreatureTypeTargetEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); + Player controller = game.getPlayer(source.getControllerId()); if (controller == null) { return; @@ -126,8 +128,6 @@ class ChangeCreatureTypeTargetEffect extends ContinuousEffectImpl { game.informPlayers(controller.getLogName() + " has chosen the creature type: " + fromSubType.toString()); } } - - super.init(source, game); } @Override @@ -140,7 +140,7 @@ class ChangeCreatureTypeTargetEffect extends ContinuousEffectImpl { throw new UnsupportedOperationException("No subtype to change set"); } boolean objectFound = false; - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { MageObject targetObject = game.getObject(targetId); if (targetObject != null) { objectFound = true; diff --git a/Mage.Sets/src/mage/cards/n/NeyaliSunsVanguard.java b/Mage.Sets/src/mage/cards/n/NeyaliSunsVanguard.java index b655d42a5ed..5195018c2c1 100644 --- a/Mage.Sets/src/mage/cards/n/NeyaliSunsVanguard.java +++ b/Mage.Sets/src/mage/cards/n/NeyaliSunsVanguard.java @@ -132,7 +132,7 @@ class NeyaliSunsVanguardEffect extends OneShotEffect { if (card == null) { return false; } - player.moveCards(card, Zone.EXILED, source, game); + player.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)); CardUtil.makeCardPlayable( game, source, card, Duration.Custom, false, source.getControllerId(), NeyaliSunsVanguardWatcher::checkPlayer diff --git a/Mage.Sets/src/mage/cards/n/NezumiGraverobber.java b/Mage.Sets/src/mage/cards/n/NezumiGraverobber.java index 1a12006d2a3..0e8c8978a75 100644 --- a/Mage.Sets/src/mage/cards/n/NezumiGraverobber.java +++ b/Mage.Sets/src/mage/cards/n/NezumiGraverobber.java @@ -19,6 +19,7 @@ import mage.constants.Outcome; import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.token.TokenImpl; @@ -73,7 +74,7 @@ class NezumiGraverobberFlipEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { Player player = game.getPlayer(card.getOwnerId()); if (player != null) { @@ -105,7 +106,7 @@ class NighteyesTheDesecratorToken extends TokenImpl { toughness = new MageInt(2); // {4}{B}: Put target creature card from a graveyard onto the battlefield under your control. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{4}{B}")); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } private NighteyesTheDesecratorToken(final NighteyesTheDesecratorToken token) { diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java b/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java index e915b137d34..5700f909cc5 100644 --- a/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java +++ b/Mage.Sets/src/mage/cards/n/NicolBolasGodPharaoh.java @@ -153,7 +153,7 @@ class NicolBolasGodPharaohPlusTwoEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/n/Nightcreep.java b/Mage.Sets/src/mage/cards/n/Nightcreep.java index b0be53899c6..518fce9de01 100644 --- a/Mage.Sets/src/mage/cards/n/Nightcreep.java +++ b/Mage.Sets/src/mage/cards/n/Nightcreep.java @@ -54,9 +54,9 @@ class NightcreepLandEffect extends BecomesBasicLandTargetEffect { @Override public void init(Ability source, Game game) { - super.init(source, game); List targets = new ArrayList<>(game.getBattlefield().getActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), source, game)); this.setTargetPointer(new FixedTargets(targets, game)); + super.init(source, game); // must call at the end due target pointer setup } @Override @@ -78,9 +78,9 @@ class NightcreepCreatureEffect extends BecomesColorTargetEffect { @Override public void init(Ability source, Game game) { - super.init(source, game); List targets = new ArrayList<>(game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)); this.setTargetPointer(new FixedTargets(targets, game)); + super.init(source, game); // must call at the end due target pointer setup } @Override diff --git a/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java b/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java index 22d2da921af..e79a9bbf471 100644 --- a/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java +++ b/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java @@ -96,7 +96,7 @@ class NightshadeAssassinEffect extends OneShotEffect { controller.revealCards(sourceObject.getIdName(), new CardsImpl(target.getTargets()), game); int unboost = target.getTargets().size() * -1; ContinuousEffect effect = new BoostTargetEffect(unboost, unboost, Duration.EndOfTurn); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); } } diff --git a/Mage.Sets/src/mage/cards/n/Nightsnare.java b/Mage.Sets/src/mage/cards/n/Nightsnare.java index 51797073edf..e8c6abcebee 100644 --- a/Mage.Sets/src/mage/cards/n/Nightsnare.java +++ b/Mage.Sets/src/mage/cards/n/Nightsnare.java @@ -50,7 +50,7 @@ class NightsnareDiscardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (player == null || controller == null) { return false; diff --git a/Mage.Sets/src/mage/cards/n/NightveilSpecter.java b/Mage.Sets/src/mage/cards/n/NightveilSpecter.java index eaa82d43d97..7029580f915 100644 --- a/Mage.Sets/src/mage/cards/n/NightveilSpecter.java +++ b/Mage.Sets/src/mage/cards/n/NightveilSpecter.java @@ -79,7 +79,7 @@ class NightveilSpecterExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Card card = player.getLibrary().getFromTop(game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java index b228c8ec8fc..3dcefa47b45 100644 --- a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java +++ b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java @@ -131,7 +131,7 @@ class NimDeathmantleEffect extends OneShotEffect { if (controller.chooseUse(Outcome.Benefit, equipment.getName() + " - Pay " + cost.getText() + '?', source, game)) { cost.clearPaid(); if (cost.pay(source, game, source, source.getControllerId(), false)) { - UUID target = targetPointer.getFirst(game, source); + UUID target = getTargetPointer().getFirst(game, source); if (target != null) { Card card = game.getCard(target); // check if it's still in graveyard diff --git a/Mage.Sets/src/mage/cards/n/NissaVoiceOfZendikar.java b/Mage.Sets/src/mage/cards/n/NissaVoiceOfZendikar.java index c9569b54ff4..9fb529546f6 100644 --- a/Mage.Sets/src/mage/cards/n/NissaVoiceOfZendikar.java +++ b/Mage.Sets/src/mage/cards/n/NissaVoiceOfZendikar.java @@ -16,7 +16,7 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterLandPermanent; import mage.game.permanent.token.PlantToken; @@ -43,7 +43,7 @@ public final class NissaVoiceOfZendikar extends CardImpl { this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new PlantToken()), 1)); // -2: Put a +1/+1 counter on each creature you control. - this.addAbility(new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()), -2)); + this.addAbility(new LoyaltyAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE), -2)); // -7: You gain X life and draw X cards, where X is the number of lands you control. Effect effect = new GainLifeEffect(new PermanentsOnBattlefieldCount(filter)); diff --git a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java index 7fb63802207..1938a494e79 100644 --- a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java +++ b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java @@ -2,23 +2,15 @@ package mage.cards.n; import java.util.UUID; -import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.condition.common.SpellMasteryCondition; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInLibrary; /** @@ -27,12 +19,24 @@ import mage.target.common.TargetCardInLibrary; */ public final class NissasPilgrimage extends CardImpl { + private static final FilterCard filter = new FilterCard("basic Forest cards"); + + static { + filter.add(SuperType.BASIC.getPredicate()); + filter.add(SubType.FOREST.getPredicate()); + } + public NissasPilgrimage(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); - // Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library. + // Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle. // Spell Mastery — If there are two or more instant and/or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two. - this.getSpellAbility().addEffect(new NissasPilgrimageEffect()); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(new TargetCardInLibrary(0, 3, filter)), + new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(new TargetCardInLibrary(0, 2, filter)), + SpellMasteryCondition.instance, + "Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle." + + "
    Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two.")); } private NissasPilgrimage(final NissasPilgrimage card) { @@ -44,57 +48,3 @@ public final class NissasPilgrimage extends CardImpl { return new NissasPilgrimage(this); } } - -class NissasPilgrimageEffect extends OneShotEffect { - - private static final FilterCard filter = new FilterCard("basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } - - public NissasPilgrimageEffect() { - super(Outcome.Benefit); - this.staticText = "Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle." - + "
    Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two."; - } - - private NissasPilgrimageEffect(final NissasPilgrimageEffect effect) { - super(effect); - } - - @Override - public NissasPilgrimageEffect copy() { - return new NissasPilgrimageEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - int number = 2; - if (SpellMasteryCondition.instance.apply(game, source)) { - number++; - } - TargetCardInLibrary target = new TargetCardInLibrary(0, number, filter); - controller.searchLibrary(target, source, game); - if (!target.getTargets().isEmpty()) { - Cards cards = new CardsImpl(target.getTargets()); - controller.revealCards(sourceObject.getIdName(), cards, game); - if (!cards.isEmpty()) { - Card card = cards.getRandom(game); - if (card != null) { - cards.remove(card); - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - controller.moveCards(cards, Zone.HAND, source, game); - } - } - } - controller.shuffleLibrary(source, game); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/n/NivMizzetGuildpact.java b/Mage.Sets/src/mage/cards/n/NivMizzetGuildpact.java new file mode 100644 index 00000000000..68e969a9492 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NivMizzetGuildpact.java @@ -0,0 +1,153 @@ +package mage.cards.n; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.HexproofFromMulticoloredAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetAnyTarget; +import mage.target.targetpointer.SecondTargetPointer; + +/** + * + * @author DominionSpy + */ +public final class NivMizzetGuildpact extends CardImpl { + + public NivMizzetGuildpact(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Hexproof from multicolored + this.addAbility(HexproofFromMulticoloredAbility.getInstance()); + + // Whenever Niv-Mizzet, Guildpact deals combat damage to a player, + // it deals X damage to any target, target player draws X cards, and you gain X life, + // where X is the number of different color pairs among permanents you control that are exactly two colors. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility( + new DamageTargetEffect(NivMizzetGuildpactCount.instance) + .setText("it deals X damage to any target"), false); + ability.addTarget(new TargetAnyTarget()); + ability.addEffect(new DrawCardTargetEffect(NivMizzetGuildpactCount.instance) + .setTargetPointer(new SecondTargetPointer()) + .setText(", target player draws X cards")); + ability.addTarget(new TargetPlayer()); + ability.addEffect(new GainLifeEffect(NivMizzetGuildpactCount.instance) + .setText(", and you gain X life, where X is the number of different color pairs " + + "among permanents you control that are exactly two colors.")); + ability.addHint(NivMizzetGuildpactHint.instance); + this.addAbility(ability); + } + + private NivMizzetGuildpact(final NivMizzetGuildpact card) { + super(card); + } + + @Override + public NivMizzetGuildpact copy() { + return new NivMizzetGuildpact(this); + } +} + +enum NivMizzetGuildpactCount implements DynamicValue { + instance; + + @Override + public NivMizzetGuildpactCount copy() { + return instance; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return (int) game.getBattlefield() + .getAllActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + sourceAbility.getControllerId(), game) + .stream() + .map(Permanent::getColor) + .filter(color -> color.getColorCount() == 2) + .distinct() + .count(); + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "the number of different color pairs among permanents you control that are exactly two colors"; + } +} + +enum NivMizzetGuildpactHint implements Hint { + instance; + + @Override + public NivMizzetGuildpactHint copy() { + return instance; + } + + @Override + public String getText(Game game, Ability ability) { + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { + return null; + } + + String hintText = "Color pairs you control: "; + + Set pairs = game.getBattlefield() + .getAllActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + ability.getControllerId(), game) + .stream() + .map(Permanent::getColor) + .filter(color -> color.getColorCount() == 2) + .collect(Collectors.toSet()); + + if (pairs.size() == 0) { + hintText += "0 (None)"; + } else { + hintText += pairs.size() + " (" + + pairs + .stream() + .map(Object::toString) + .sorted() + .map(string -> "{" + string.charAt(0) + "}{" + string.charAt(1) + "}") + .collect(Collectors.joining(", ")) + ")"; + } + + return hintText; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NobleStand.java b/Mage.Sets/src/mage/cards/n/NobleStand.java index 4ecb286a742..cc5874fd601 100644 --- a/Mage.Sets/src/mage/cards/n/NobleStand.java +++ b/Mage.Sets/src/mage/cards/n/NobleStand.java @@ -1,4 +1,3 @@ - package mage.cards.n; import java.util.UUID; @@ -8,8 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.permanent.TokenPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -39,11 +37,12 @@ public final class NobleStand extends CardImpl { class NobleStandAbility extends TriggeredAbilityImpl { - public NobleStandAbility() { + NobleStandAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(2)); + setTriggerPhrase("Whenever a creature you control blocks, "); } - public NobleStandAbility(final mage.cards.n.NobleStandAbility ability) { + private NobleStandAbility(final NobleStandAbility ability) { super(ability); } @@ -54,19 +53,12 @@ class NobleStandAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - filter.add(TokenPredicate.FALSE); Permanent permanent = game.getPermanent(event.getSourceId()); - return permanent != null && filter.match(permanent, controllerId, this, game); + return permanent != null && StaticFilters.FILTER_CONTROLLED_CREATURE.match(permanent, controllerId, this, game); } @Override - public String getRule() { - return "Whenever a creature you control blocks, you gain 2 life."; - } - - @Override - public mage.cards.n.NobleStandAbility copy() { - return new mage.cards.n.NobleStandAbility(this); + public NobleStandAbility copy() { + return new NobleStandAbility(this); } } diff --git a/Mage.Sets/src/mage/cards/n/NomadsAssembly.java b/Mage.Sets/src/mage/cards/n/NomadsAssembly.java index 5bc78772a53..18a3dd1c86a 100644 --- a/Mage.Sets/src/mage/cards/n/NomadsAssembly.java +++ b/Mage.Sets/src/mage/cards/n/NomadsAssembly.java @@ -1,4 +1,3 @@ - package mage.cards.n; import java.util.UUID; @@ -8,7 +7,7 @@ import mage.abilities.keyword.ReboundAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.permanent.token.KorSoldierToken; /** @@ -17,13 +16,11 @@ import mage.game.permanent.token.KorSoldierToken; */ public final class NomadsAssembly extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - public NomadsAssembly(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{W}{W}"); - - this.getSpellAbility().addEffect(new CreateTokenEffect(new KorSoldierToken(), new PermanentsOnBattlefieldCount(filter))); + // Create a 1/1 white Kor Soldier creature token for each creature you control. + this.getSpellAbility().addEffect(new CreateTokenEffect(new KorSoldierToken(), new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE))); this.addAbility(new ReboundAbility()); } diff --git a/Mage.Sets/src/mage/cards/n/NovaPentacle.java b/Mage.Sets/src/mage/cards/n/NovaPentacle.java index 6637de77084..7ab6107cfa0 100644 --- a/Mage.Sets/src/mage/cards/n/NovaPentacle.java +++ b/Mage.Sets/src/mage/cards/n/NovaPentacle.java @@ -69,8 +69,8 @@ class NovaPentacleEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { - this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/n/NoyanDarRoilShaper.java b/Mage.Sets/src/mage/cards/n/NoyanDarRoilShaper.java index 5d9efd51085..4eb59533a7b 100644 --- a/Mage.Sets/src/mage/cards/n/NoyanDarRoilShaper.java +++ b/Mage.Sets/src/mage/cards/n/NoyanDarRoilShaper.java @@ -89,12 +89,12 @@ class NoyanDarEffect extends OneShotEffect { targetId = target.getFirstTarget(); } if (targetId != null) { - FixedTarget fixedTarget = new FixedTarget(targetId, game); + FixedTarget blueprintTarget = new FixedTarget(targetId, game); ContinuousEffect continuousEffect = new BecomesCreatureTargetEffect(new AwakenElementalToken(), false, true, Duration.EndOfGame); - continuousEffect.setTargetPointer(fixedTarget); + continuousEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(continuousEffect, source); Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(3)); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); return effect.apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/o/OathOfAjani.java b/Mage.Sets/src/mage/cards/o/OathOfAjani.java index e2ebd47f5f2..820aa6d7c0d 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfAjani.java +++ b/Mage.Sets/src/mage/cards/o/OathOfAjani.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.SuperType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterPlaneswalkerCard; /** @@ -26,7 +26,7 @@ public final class OathOfAjani extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // When Oath of Ajani enters the battlefield, put a +1/+1 counter on each creature you control. - this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE))); // Planeswalker spells you cast cost {1} less to cast. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SpellsCostReductionControllerEffect(new FilterPlaneswalkerCard("Planeswalker spells"), 1))); diff --git a/Mage.Sets/src/mage/cards/o/Oblation.java b/Mage.Sets/src/mage/cards/o/Oblation.java index 5665e250225..8fd0b5b9cbe 100644 --- a/Mage.Sets/src/mage/cards/o/Oblation.java +++ b/Mage.Sets/src/mage/cards/o/Oblation.java @@ -56,7 +56,7 @@ class OblationEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { Player player = game.getPlayer(permanent.getOwnerId()); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java b/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java index 4524d59ef79..d36d5725dae 100644 --- a/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java +++ b/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java @@ -134,7 +134,7 @@ class ObsidianFireheartGainAbilityEffect extends GainAbilityTargetEffect { @Override public boolean isInactive(Ability source, Game game) { - Permanent targetLand = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent targetLand = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (targetLand != null && targetLand.getCounters(game).getCount(CounterType.BLAZE) < 1) { return true; diff --git a/Mage.Sets/src/mage/cards/o/Occupation.java b/Mage.Sets/src/mage/cards/o/Occupation.java index 53d0c84e57d..8b4e10f5a2c 100644 --- a/Mage.Sets/src/mage/cards/o/Occupation.java +++ b/Mage.Sets/src/mage/cards/o/Occupation.java @@ -159,7 +159,7 @@ class OccupationRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OfficiousInterrogation.java b/Mage.Sets/src/mage/cards/o/OfficiousInterrogation.java new file mode 100644 index 00000000000..19cf735df76 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OfficiousInterrogation.java @@ -0,0 +1,123 @@ +package mage.cards.o; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.Target; +import mage.target.TargetPlayer; + +/** + * + * @author DominionSpy + */ +public final class OfficiousInterrogation extends CardImpl { + + public OfficiousInterrogation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}{U}"); + + // This spell costs {W}{U} more to cast for each target beyond the first. + this.addAbility(new SimpleStaticAbility(Zone.STACK, new OfficiousInterrogationCostIncreasingEffect()) + .setRuleAtTheTop(true)); + + // Choose any number of target players. Investigate X times, where X is the total number of creatures those players control. + this.getSpellAbility().addEffect(new InvestigateEffect(OfficiousInterrogationCount.instance) + .setText("Choose any number of target players. Investigate X times, where X is " + + "the total number of creatures those players control.")); + this.getSpellAbility().addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + } + + private OfficiousInterrogation(final OfficiousInterrogation card) { + super(card); + } + + @Override + public OfficiousInterrogation copy() { + return new OfficiousInterrogation(this); + } +} + +class OfficiousInterrogationCostIncreasingEffect extends CostModificationEffectImpl { + + OfficiousInterrogationCostIncreasingEffect() { + super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.INCREASE_COST); + staticText = "This spell costs {W}{U} more to cast for each target beyond the first."; + } + + private OfficiousInterrogationCostIncreasingEffect(final OfficiousInterrogationCostIncreasingEffect effect) { + super(effect); + } + + @Override + public OfficiousInterrogationCostIncreasingEffect copy() { + return new OfficiousInterrogationCostIncreasingEffect(this); + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify.getSourceId().equals(source.getSourceId()) && + (abilityToModify instanceof SpellAbility); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Target target = abilityToModify.getTargets().get(0); + int additionalTargets = target.getTargets().size() - 1; + if (additionalTargets > 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < additionalTargets; i++) { + sb.append("{W}{U}"); + } + abilityToModify.addManaCostsToPay(new ManaCostsImpl<>(sb.toString())); + return true; + } + return false; + } +} + +enum OfficiousInterrogationCount implements DynamicValue { + instance; + + @Override + public OfficiousInterrogationCount copy() { + return instance; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int totalCreatureCount = 0; + + Target target = sourceAbility.getTargets().get(0); + for (UUID playerId : target.getTargets()) { + totalCreatureCount += game.getBattlefield() + .count(StaticFilters.FILTER_CONTROLLED_CREATURE, playerId, sourceAbility, game); + } + + return totalCreatureCount; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "the total number of creatures those players control"; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OgreChitterlord.java b/Mage.Sets/src/mage/cards/o/OgreChitterlord.java index 89b7cccea8f..46080bab18b 100644 --- a/Mage.Sets/src/mage/cards/o/OgreChitterlord.java +++ b/Mage.Sets/src/mage/cards/o/OgreChitterlord.java @@ -48,7 +48,7 @@ public final class OgreChitterlord extends CardImpl { // Whenever Ogre Chitterlord enters the battlefield or attacks, create two 1/1 black Rat creature tokens with "This creature can't block." Then if you control five or more Rats, each Rat you control gets +2/+0 until end of turn. Ability ability = new OrTriggeredAbility( Zone.BATTLEFIELD, new CreateTokenEffect(new RatCantBlockToken(), 2), - false, "whenever {this} enters the battlefield or attacks, ", + false, "Whenever {this} enters the battlefield or attacks, ", new EntersBattlefieldTriggeredAbility(null), new AttacksTriggeredAbility(null) ); diff --git a/Mage.Sets/src/mage/cards/o/OgrePainbringer.java b/Mage.Sets/src/mage/cards/o/OgrePainbringer.java deleted file mode 100644 index b1b1d4e0ce3..00000000000 --- a/Mage.Sets/src/mage/cards/o/OgrePainbringer.java +++ /dev/null @@ -1,38 +0,0 @@ -package mage.cards.o; - -import mage.MageInt; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.DamagePlayersEffect; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.TargetController; - -import java.util.UUID; - -/** - * @author JayDi85 - */ -public final class OgrePainbringer extends CardImpl { - - public OgrePainbringer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); - this.subtype.add(SubType.OGRE); - - this.power = new MageInt(7); - this.toughness = new MageInt(3); - - // When Ogre Painbringer enters the battlefield, it deals 3 damage to each player. - this.addAbility(new EntersBattlefieldTriggeredAbility(new DamagePlayersEffect(3, TargetController.ANY, "it"))); - } - - private OgrePainbringer(final OgrePainbringer card) { - super(card); - } - - @Override - public OgrePainbringer copy() { - return new OgrePainbringer(this); - } -} diff --git a/Mage.Sets/src/mage/cards/o/OhabiCaleria.java b/Mage.Sets/src/mage/cards/o/OhabiCaleria.java index 609e14f08eb..927510fb7f5 100644 --- a/Mage.Sets/src/mage/cards/o/OhabiCaleria.java +++ b/Mage.Sets/src/mage/cards/o/OhabiCaleria.java @@ -11,7 +11,7 @@ import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -20,8 +20,8 @@ import java.util.UUID; */ public final class OhabiCaleria extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.ARCHER, "Archers you control"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent(SubType.ARCHER, "an Archer you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ARCHER, "Archers you control"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.ARCHER, "an Archer you control"); public OhabiCaleria(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}"); diff --git a/Mage.Sets/src/mage/cards/o/OlagLudevicsHubris.java b/Mage.Sets/src/mage/cards/o/OlagLudevicsHubris.java index d21012cbf8b..9b9a6abc671 100644 --- a/Mage.Sets/src/mage/cards/o/OlagLudevicsHubris.java +++ b/Mage.Sets/src/mage/cards/o/OlagLudevicsHubris.java @@ -88,7 +88,6 @@ class OlagLudevicsHubrisEffect extends ReplacementEffectImpl { CopyApplier applier = new OlagLudevicsHubrisCopyApplier(); applier.apply(game, newBluePrint, source, source.getSourceId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, newBluePrint, source.getSourceId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/o/OmenMachine.java b/Mage.Sets/src/mage/cards/o/OmenMachine.java index 5e2d820a6ab..53252bbce4f 100644 --- a/Mage.Sets/src/mage/cards/o/OmenMachine.java +++ b/Mage.Sets/src/mage/cards/o/OmenMachine.java @@ -87,7 +87,7 @@ class OmenMachineEffect2 extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Card card = player.getLibrary().getFromTop(game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/o/OnTheTrail.java b/Mage.Sets/src/mage/cards/o/OnTheTrail.java new file mode 100644 index 00000000000..5c316608452 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OnTheTrail.java @@ -0,0 +1,36 @@ +package mage.cards.o; + +import mage.abilities.common.DrawNthCardTriggeredAbility; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class OnTheTrail extends CardImpl { + + public OnTheTrail(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + + // Whenever you draw your second card each turn, you may put a land card from your hand onto the battlefield tapped. + this.addAbility(new DrawNthCardTriggeredAbility( + new PutCardFromHandOntoBattlefieldEffect( + StaticFilters.FILTER_CARD_LAND_A, false, true + ), false, 2 + )); + } + + private OnTheTrail(final OnTheTrail card) { + super(card); + } + + @Override + public OnTheTrail copy() { + return new OnTheTrail(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OnduCleric.java b/Mage.Sets/src/mage/cards/o/OnduCleric.java index 9c0069c83d7..d4cd96713ca 100644 --- a/Mage.Sets/src/mage/cards/o/OnduCleric.java +++ b/Mage.Sets/src/mage/cards/o/OnduCleric.java @@ -10,8 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -19,12 +18,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class OnduCleric extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Allies you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "Allies you control"); public OnduCleric(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}"); @@ -35,6 +29,7 @@ public final class OnduCleric extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); + // Whenever Ondu Cleric or another Ally enters the battlefield under your control, you may gain life equal to the number of Allies you control. this.addAbility(new AllyEntersBattlefieldTriggeredAbility(new GainLifeEffect(new PermanentsOnBattlefieldCount(filter)), true).setAbilityWord(null)); } diff --git a/Mage.Sets/src/mage/cards/o/OnduRising.java b/Mage.Sets/src/mage/cards/o/OnduRising.java index 51519a81ea9..273cf295063 100644 --- a/Mage.Sets/src/mage/cards/o/OnduRising.java +++ b/Mage.Sets/src/mage/cards/o/OnduRising.java @@ -1,4 +1,3 @@ - package mage.cards.o; import java.util.UUID; @@ -14,7 +13,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -28,7 +26,7 @@ public final class OnduRising extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{W}"); // Whenever a creature attacks this turn, it gains lifelink until end of turn. - this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new OnduRisingTriggeredAbility())); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new OnduRisingTriggeredAbility(), false)); // Awaken 4—{4}{W} this.addAbility(new AwakenAbility(this, 4, "{4}{W}")); diff --git a/Mage.Sets/src/mage/cards/o/OnduWarCleric.java b/Mage.Sets/src/mage/cards/o/OnduWarCleric.java index 216a35f8fef..8d1d2d24c4a 100644 --- a/Mage.Sets/src/mage/cards/o/OnduWarCleric.java +++ b/Mage.Sets/src/mage/cards/o/OnduWarCleric.java @@ -1,22 +1,14 @@ - package mage.cards.o; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @@ -24,13 +16,6 @@ import mage.target.common.TargetControlledPermanent; */ public final class OnduWarCleric extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public OnduWarCleric(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}"); this.subtype.add(SubType.HUMAN); @@ -40,10 +25,7 @@ public final class OnduWarCleric extends CardImpl { this.toughness = new MageInt(2); // Cohort — {T}, Tap an untapped Ally you control: You gain 2 life. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(2), new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); - ability.setAbilityWord(AbilityWord.COHORT); - this.addAbility(ability); + this.addAbility(new CohortAbility(new GainLifeEffect(2))); } private OnduWarCleric(final OnduWarCleric card) { diff --git a/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java b/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java index 8a7780b1d9b..296c5d7342f 100644 --- a/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java +++ b/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java @@ -77,8 +77,8 @@ class OpalEyeKondasYojimboRedirectionEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OpalTitan.java b/Mage.Sets/src/mage/cards/o/OpalTitan.java index 27b8f90f373..2f7e0167e9d 100644 --- a/Mage.Sets/src/mage/cards/o/OpalTitan.java +++ b/Mage.Sets/src/mage/cards/o/OpalTitan.java @@ -66,7 +66,7 @@ class OpalTitanBecomesCreatureEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); affectedObjectList.add(new MageObjectReference(source.getSourceId(), game)); - Spell creatureSpellCast = game.getSpell(targetPointer.getFirst(game, source)); + Spell creatureSpellCast = game.getSpell(getTargetPointer().getFirst(game, source)); if (creatureSpellCast != null && creatureSpellCast.getColor(game).hasColor()) { game.getState().setValue("opalTitanColor" + source.getSourceId(), creatureSpellCast.getColor(game)); diff --git a/Mage.Sets/src/mage/cards/o/OpenTheGates.java b/Mage.Sets/src/mage/cards/o/OpenTheGates.java index a30e7575ad0..ddc7595fbe1 100644 --- a/Mage.Sets/src/mage/cards/o/OpenTheGates.java +++ b/Mage.Sets/src/mage/cards/o/OpenTheGates.java @@ -17,7 +17,7 @@ import java.util.UUID; */ public final class OpenTheGates extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic land card or a Gate card"); + private static final FilterCard filter = new FilterCard("a basic land card or Gate card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/o/OppressiveWill.java b/Mage.Sets/src/mage/cards/o/OppressiveWill.java index 2929570bb26..4eb94ee6e70 100644 --- a/Mage.Sets/src/mage/cards/o/OppressiveWill.java +++ b/Mage.Sets/src/mage/cards/o/OppressiveWill.java @@ -58,7 +58,7 @@ class SpellSyphonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (sourceObject != null && spell != null) { Player player = game.getPlayer(spell.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/o/OraclesAttendants.java b/Mage.Sets/src/mage/cards/o/OraclesAttendants.java index 34af66ff2b6..1e190ad2016 100644 --- a/Mage.Sets/src/mage/cards/o/OraclesAttendants.java +++ b/Mage.Sets/src/mage/cards/o/OraclesAttendants.java @@ -74,6 +74,7 @@ class OraclesAttendantsReplacementEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/o/OrnateKanzashi.java b/Mage.Sets/src/mage/cards/o/OrnateKanzashi.java index 4163ccf43b8..28522d235f1 100644 --- a/Mage.Sets/src/mage/cards/o/OrnateKanzashi.java +++ b/Mage.Sets/src/mage/cards/o/OrnateKanzashi.java @@ -71,7 +71,7 @@ class OrnateKanzashiEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (sourceObject != null && opponent != null) { if (opponent.getLibrary().hasCards()) { diff --git a/Mage.Sets/src/mage/cards/o/OrochiEggwatcher.java b/Mage.Sets/src/mage/cards/o/OrochiEggwatcher.java index c9a68065064..6e085eb5c10 100644 --- a/Mage.Sets/src/mage/cards/o/OrochiEggwatcher.java +++ b/Mage.Sets/src/mage/cards/o/OrochiEggwatcher.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.cards.o; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/o/OtharriSunsGlory.java b/Mage.Sets/src/mage/cards/o/OtharriSunsGlory.java index c1e988f872c..ad6fd708381 100644 --- a/Mage.Sets/src/mage/cards/o/OtharriSunsGlory.java +++ b/Mage.Sets/src/mage/cards/o/OtharriSunsGlory.java @@ -18,7 +18,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.token.RebelRedToken; @@ -32,7 +32,7 @@ import java.util.UUID; */ public final class OtharriSunsGlory extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Rebel you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Rebel you control"); static { filter.add(TappedPredicate.UNTAPPED); diff --git a/Mage.Sets/src/mage/cards/o/Outmaneuver.java b/Mage.Sets/src/mage/cards/o/Outmaneuver.java index 378585bfa82..d3c5f503a33 100644 --- a/Mage.Sets/src/mage/cards/o/Outmaneuver.java +++ b/Mage.Sets/src/mage/cards/o/Outmaneuver.java @@ -70,7 +70,7 @@ class OutmaneuverEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - return targetPointer.getTargets(game, source).contains(sourceId); + return getTargetPointer().getTargets(game, source).contains(sourceId); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OutrageousRobbery.java b/Mage.Sets/src/mage/cards/o/OutrageousRobbery.java new file mode 100644 index 00000000000..8d889a7042d --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutrageousRobbery.java @@ -0,0 +1,100 @@ +package mage.cards.o; + +import java.util.Set; +import java.util.UUID; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class OutrageousRobbery extends CardImpl { + + public OutrageousRobbery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{B}{B}"); + + // Target opponent exiles the top X cards of their library face down. + // You may look at and play those cards for as long as they remain exiled. + // If you cast a spell this way, you may spend mana as though it were mana of any type to cast it. + this.getSpellAbility().addEffect(new OutrageousRobberyEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private OutrageousRobbery(final OutrageousRobbery card) { + super(card); + } + + @Override + public OutrageousRobbery copy() { + return new OutrageousRobbery(this); + } +} + +class OutrageousRobberyEffect extends OneShotEffect { + + OutrageousRobberyEffect() { + super(Outcome.Benefit); + this.staticText = "Target opponent exiles the top X cards of their library face down. " + + "You may look at and play those cards for as long as they remain exiled. " + + "If you cast a spell this way, you may spend mana as though it were mana of any type to cast it."; + } + + private OutrageousRobberyEffect(final OutrageousRobberyEffect effect) { + super(effect); + } + + @Override + public OutrageousRobberyEffect copy() { + return new OutrageousRobberyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(source.getFirstTarget()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + + int xValue = source.getManaCostsToPay().getX(); + if (xValue == 0) { + return false; + } + + Set cards = opponent.getLibrary().getTopCards(game, xValue); + cards.forEach(card -> card.setFaceDown(true, game)); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + if (cards.size() > 0 && opponent.moveCardsToExile(cards, source, game, false, exileZoneId, + sourceObject.getIdName() + " (" + controller.getName() + ")")) { + for (Card card : cards) { + card.setFaceDown(true, game); + + ContinuousEffect effect = new MayLookAtTargetCardEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + + CardUtil.makeCardPlayable( + game, source, card, Duration.Custom, true, + source.getControllerId(), null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OverflowingBasin.java b/Mage.Sets/src/mage/cards/o/OverflowingBasin.java new file mode 100644 index 00000000000..1942e488a43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OverflowingBasin.java @@ -0,0 +1,37 @@ +package mage.cards.o; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OverflowingBasin extends CardImpl { + + public OverflowingBasin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {1}, {T}: Add {G}{U}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new Mana(0, 1, 0, 0, 1, 0, 0, 0), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private OverflowingBasin(final OverflowingBasin card) { + super(card); + } + + @Override + public OverflowingBasin copy() { + return new OverflowingBasin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OvergrowthElemental.java b/Mage.Sets/src/mage/cards/o/OvergrowthElemental.java index 67fe98e559b..d59fe2bb6ea 100644 --- a/Mage.Sets/src/mage/cards/o/OvergrowthElemental.java +++ b/Mage.Sets/src/mage/cards/o/OvergrowthElemental.java @@ -16,7 +16,7 @@ import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -30,7 +30,7 @@ import java.util.UUID; public final class OvergrowthElemental extends CardImpl { private static final FilterPermanent filter - = new FilterControlledCreaturePermanent(SubType.ELEMENTAL, "another target Elemental you control"); + = new FilterControlledPermanent(SubType.ELEMENTAL, "another target Elemental you control"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/o/OverrideCard.java b/Mage.Sets/src/mage/cards/o/OverrideCard.java index e99588336cb..187de85d4b8 100644 --- a/Mage.Sets/src/mage/cards/o/OverrideCard.java +++ b/Mage.Sets/src/mage/cards/o/OverrideCard.java @@ -59,7 +59,7 @@ class OverrideEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (sourceObject != null && spell != null) { Player player = game.getPlayer(spell.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/o/Overtaker.java b/Mage.Sets/src/mage/cards/o/Overtaker.java index 0a470a8140e..c710da37192 100644 --- a/Mage.Sets/src/mage/cards/o/Overtaker.java +++ b/Mage.Sets/src/mage/cards/o/Overtaker.java @@ -38,10 +38,10 @@ public final class Overtaker extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new UntapTargetEffect(), new ManaCostsImpl<>("{3}{U}")); ability.addCost(new TapSourceCost()); ability.addCost(new DiscardCardCost()); - Effect effect = new GainControlTargetEffect(Duration.EndOfTurn); - ability.addEffect(effect); - effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - ability.addEffect(effect); + ability.addEffect(new GainControlTargetEffect(Duration.EndOfTurn) + .setText("and gain control of it until end of turn")); + ability.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn) + .setText("that creature gains haste until end of turn")); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PainfulQuandary.java b/Mage.Sets/src/mage/cards/p/PainfulQuandary.java index bb19a7666c0..30bd2151440 100644 --- a/Mage.Sets/src/mage/cards/p/PainfulQuandary.java +++ b/Mage.Sets/src/mage/cards/p/PainfulQuandary.java @@ -60,7 +60,7 @@ class PainfulQuandryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { boolean paid = false; Cost cost = new DiscardTargetCost(new TargetCardInHand()); diff --git a/Mage.Sets/src/mage/cards/p/PashalikMons.java b/Mage.Sets/src/mage/cards/p/PashalikMons.java index b160ba2d6b8..dbf34c9f14d 100644 --- a/Mage.Sets/src/mage/cards/p/PashalikMons.java +++ b/Mage.Sets/src/mage/cards/p/PashalikMons.java @@ -14,13 +14,11 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.permanent.token.GoblinToken; import mage.target.common.TargetAnyTarget; -import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -32,7 +30,7 @@ public final class PashalikMons extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.GOBLIN, "Goblin you control"); private static final FilterControlledPermanent filter2 - = new FilterControlledCreaturePermanent(SubType.GOBLIN, "a Goblin"); + = new FilterControlledPermanent(SubType.GOBLIN, "a Goblin"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java index 73fb8b5eabd..f2b8b1bf455 100644 --- a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -20,7 +20,7 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -111,7 +111,7 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(); static { filter.add(SubType.VAMPIRE.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/p/PeachGardenOath.java b/Mage.Sets/src/mage/cards/p/PeachGardenOath.java index 2e276672ec4..88aa0536f22 100644 --- a/Mage.Sets/src/mage/cards/p/PeachGardenOath.java +++ b/Mage.Sets/src/mage/cards/p/PeachGardenOath.java @@ -8,7 +8,7 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -19,9 +19,8 @@ public final class PeachGardenOath extends CardImpl { public PeachGardenOath(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{W}"); - // You gain 2 life for each creature you control. - DynamicValue amount = new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent(), 2); + DynamicValue amount = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE, 2); this.getSpellAbility().addEffect(new GainLifeEffect(amount)); } diff --git a/Mage.Sets/src/mage/cards/p/PeatBog.java b/Mage.Sets/src/mage/cards/p/PeatBog.java index 1b9d54e3d63..efcfde01a27 100644 --- a/Mage.Sets/src/mage/cards/p/PeatBog.java +++ b/Mage.Sets/src/mage/cards/p/PeatBog.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,8 +30,11 @@ public final class PeatBog extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); // Peat Bog enters the battlefield tapped with two depletion counters on it. - this.addAbility(new EntersBattlefieldTappedAbility()); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2)))); + Ability etbAbility = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with two depletion counters on it" + ); + etbAbility.addEffect(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2))); + this.addAbility(etbAbility); // {T}, Remove a depletion counter from Peat Bog: Add {B}{B}. If there are no depletion counters on Peat Bog, sacrifice it. Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(2), new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.DEPLETION.createInstance(1))); diff --git a/Mage.Sets/src/mage/cards/p/Penance.java b/Mage.Sets/src/mage/cards/p/Penance.java index e28674f1951..5f44789f2ae 100644 --- a/Mage.Sets/src/mage/cards/p/Penance.java +++ b/Mage.Sets/src/mage/cards/p/Penance.java @@ -63,8 +63,8 @@ class PenanceEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/Peregrination.java b/Mage.Sets/src/mage/cards/p/Peregrination.java index 00698583796..8795a057aa0 100644 --- a/Mage.Sets/src/mage/cards/p/Peregrination.java +++ b/Mage.Sets/src/mage/cards/p/Peregrination.java @@ -1,22 +1,13 @@ package mage.cards.p; -import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.cards.*; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; -import java.util.Set; import java.util.UUID; /** @@ -27,8 +18,10 @@ public final class Peregrination extends CardImpl { public Peregrination(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); - // Seach your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle your library, then scry 1. - this.getSpellAbility().addEffect(new PeregrinationEffect()); + // Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle, then scry 1. + this.getSpellAbility().addEffect(new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect( + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS)) + .setText("search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle")); Effect effect = new ScryEffect(1); effect.concatBy(", then"); this.getSpellAbility().addEffect(effect); @@ -43,63 +36,3 @@ public final class Peregrination extends CardImpl { return new Peregrination(this); } } - -class PeregrinationEffect extends OneShotEffect { - - protected static final FilterCard filter = new FilterCard("card to put on the battlefield tapped"); - - public PeregrinationEffect() { - super(Outcome.PutLandInPlay); - staticText = "Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle"; - } - - private PeregrinationEffect(final PeregrinationEffect effect) { - super(effect); - } - - @Override - public PeregrinationEffect copy() { - return new PeregrinationEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); - if (controller == null || sourceObject == null) { - return false; - } - TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, source, game)) { - if (!target.getTargets().isEmpty()) { - Cards revealed = new CardsImpl(); - for (UUID cardId : target.getTargets()) { - Card card = controller.getLibrary().getCard(cardId, game); - revealed.add(card); - } - controller.revealCards(sourceObject.getIdName(), revealed, game); - if (target.getTargets().size() == 2) { - TargetCard target2 = new TargetCard(Zone.LIBRARY, filter); - controller.choose(Outcome.Benefit, revealed, target2, source, game); - Card card = revealed.get(target2.getFirstTarget(), game); - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - revealed.remove(card); - Set cards = revealed.getCards(game); - card = cards.isEmpty() ? null : cards.iterator().next(); - controller.moveCards(card, Zone.HAND, source, game); - } else if (target.getTargets().size() == 1) { - Set cards = revealed.getCards(game); - Card card = cards.isEmpty() ? null : cards.iterator().next(); - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - } - - } - controller.shuffleLibrary(source, game); - return true; - } - controller.shuffleLibrary(source, game); - return false; - - } - -} diff --git a/Mage.Sets/src/mage/cards/p/PerplexingChimera.java b/Mage.Sets/src/mage/cards/p/PerplexingChimera.java index 6802f024a9d..6df97cfb5f8 100644 --- a/Mage.Sets/src/mage/cards/p/PerplexingChimera.java +++ b/Mage.Sets/src/mage/cards/p/PerplexingChimera.java @@ -146,7 +146,7 @@ class PerplexingChimeraControlExchangeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (spell != null && controller != null) { Player spellCaster = game.getPlayer(spell.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/p/PersuasiveInterrogators.java b/Mage.Sets/src/mage/cards/p/PersuasiveInterrogators.java index 138740f06bd..01fcb739d81 100644 --- a/Mage.Sets/src/mage/cards/p/PersuasiveInterrogators.java +++ b/Mage.Sets/src/mage/cards/p/PersuasiveInterrogators.java @@ -10,7 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetOpponent; import java.util.UUID; @@ -20,8 +20,6 @@ import java.util.UUID; */ public final class PersuasiveInterrogators extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "a Clue"); - public PersuasiveInterrogators(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); @@ -34,7 +32,7 @@ public final class PersuasiveInterrogators extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new InvestigateEffect())); // Whenever you sacrifice a Clue, target opponent gets two poison counters. - Ability ability = new SacrificePermanentTriggeredAbility(new AddPoisonCounterTargetEffect(2), filter); + Ability ability = new SacrificePermanentTriggeredAbility(new AddPoisonCounterTargetEffect(2), StaticFilters.FILTER_CONTROLLED_CLUE); ability.addTarget(new TargetOpponent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PestilentSpirit.java b/Mage.Sets/src/mage/cards/p/PestilentSpirit.java index d8842ffcd44..e3ac73f936c 100644 --- a/Mage.Sets/src/mage/cards/p/PestilentSpirit.java +++ b/Mage.Sets/src/mage/cards/p/PestilentSpirit.java @@ -10,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import java.util.UUID; @@ -20,7 +20,7 @@ import java.util.UUID; */ public final class PestilentSpirit extends CardImpl { - private static final FilterCard filter = new FilterCard("instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("instant and sorcery spells you control"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/p/PetraSphinx.java b/Mage.Sets/src/mage/cards/p/PetraSphinx.java index 3fbfaab094b..9766ce8cf30 100644 --- a/Mage.Sets/src/mage/cards/p/PetraSphinx.java +++ b/Mage.Sets/src/mage/cards/p/PetraSphinx.java @@ -61,7 +61,7 @@ class PetraSphinxEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || player == null || !player.getLibrary().hasCards()) { return true; } diff --git a/Mage.Sets/src/mage/cards/p/Petradon.java b/Mage.Sets/src/mage/cards/p/Petradon.java index 77b0fe40694..634ae5333d8 100644 --- a/Mage.Sets/src/mage/cards/p/Petradon.java +++ b/Mage.Sets/src/mage/cards/p/Petradon.java @@ -15,7 +15,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.StaticFilters; import mage.target.common.TargetLandPermanent; import java.util.UUID; @@ -38,7 +37,8 @@ public final class Petradon extends CardImpl { this.addAbility(ability); // When Petradon leaves the battlefield, return the exiled cards to the battlefield under their owners' control. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD).withText(true, true), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD) + .withText(true, true, false), false)); // {R}: Petradon gets +1/+0 until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl<>("{R}"))); diff --git a/Mage.Sets/src/mage/cards/p/PhalanxLeader.java b/Mage.Sets/src/mage/cards/p/PhalanxLeader.java index 1aacdb86996..c768c627704 100644 --- a/Mage.Sets/src/mage/cards/p/PhalanxLeader.java +++ b/Mage.Sets/src/mage/cards/p/PhalanxLeader.java @@ -10,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -27,7 +27,7 @@ public final class PhalanxLeader extends CardImpl { this.toughness = new MageInt(1); // Heroic Whenever you cast a spell that targets Phalanx Leader, put a +1/+1 counter on each creature you control. - this.addAbility(new HeroicAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), new FilterControlledCreaturePermanent()))); + this.addAbility(new HeroicAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE))); } private PhalanxLeader(final PhalanxLeader card) { diff --git a/Mage.Sets/src/mage/cards/p/PhantasmalTerrain.java b/Mage.Sets/src/mage/cards/p/PhantasmalTerrain.java index 04729f4ed4e..65008b845e7 100644 --- a/Mage.Sets/src/mage/cards/p/PhantasmalTerrain.java +++ b/Mage.Sets/src/mage/cards/p/PhantasmalTerrain.java @@ -69,6 +69,11 @@ public final class PhantasmalTerrain extends CardImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + discard(); + return; + } + switch (choice) { case FOREST: dependencyTypes.add(DependencyType.BecomeForest); diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianTyranny.java b/Mage.Sets/src/mage/cards/p/PhyrexianTyranny.java index be7a873862c..bb5e78de179 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianTyranny.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianTyranny.java @@ -95,7 +95,7 @@ class PhyrexianTyrannyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Cost cost = ManaUtil.createManaCost(2, false); if (!cost.pay(source, game, source, player.getId(), false, null)) { diff --git a/Mage.Sets/src/mage/cards/p/PilgrimOfJustice.java b/Mage.Sets/src/mage/cards/p/PilgrimOfJustice.java index 56651fbd1cc..521347adba6 100644 --- a/Mage.Sets/src/mage/cards/p/PilgrimOfJustice.java +++ b/Mage.Sets/src/mage/cards/p/PilgrimOfJustice.java @@ -82,6 +82,7 @@ class PilgrimOfJusticeEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/p/PilgrimOfVirtue.java b/Mage.Sets/src/mage/cards/p/PilgrimOfVirtue.java index a4d8a6ef104..f7fa244cd89 100644 --- a/Mage.Sets/src/mage/cards/p/PilgrimOfVirtue.java +++ b/Mage.Sets/src/mage/cards/p/PilgrimOfVirtue.java @@ -82,6 +82,7 @@ class PilgrimOfVirtueEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/p/PineWalker.java b/Mage.Sets/src/mage/cards/p/PineWalker.java index 1a80aee7bf5..05b3cd664e1 100644 --- a/Mage.Sets/src/mage/cards/p/PineWalker.java +++ b/Mage.Sets/src/mage/cards/p/PineWalker.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -12,7 +11,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.FilterPermanentThisOrAnother; +import mage.filter.StaticFilters; /** * @@ -20,6 +20,8 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class PineWalker extends CardImpl { + private static final FilterPermanentThisOrAnother filter = new FilterPermanentThisOrAnother(StaticFilters.FILTER_CONTROLLED_CREATURE, true); + public PineWalker(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); this.subtype.add(SubType.ELEMENTAL); @@ -32,7 +34,7 @@ public final class PineWalker extends CardImpl { // Whenever Pine Walker or another creature you control is turned face up, untap that creature. Effect effect = new UntapTargetEffect(); effect.setText("untap that creature"); - this.addAbility(new TurnedFaceUpAllTriggeredAbility(effect, new FilterControlledCreaturePermanent("{this} or another creature you control"), true)); + this.addAbility(new TurnedFaceUpAllTriggeredAbility(effect, filter, true)); } diff --git a/Mage.Sets/src/mage/cards/p/PlagueReaver.java b/Mage.Sets/src/mage/cards/p/PlagueReaver.java index 5848c334ef0..81391af130a 100644 --- a/Mage.Sets/src/mage/cards/p/PlagueReaver.java +++ b/Mage.Sets/src/mage/cards/p/PlagueReaver.java @@ -182,7 +182,7 @@ class PlagueReaverReturnEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(playerId); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); return player != null && card != null && player.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java b/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java index cf7a85ca3e3..dca3c1b02c5 100644 --- a/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java +++ b/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java @@ -51,7 +51,7 @@ class PlaneswalkersFuryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (opponent != null && !opponent.getHand().isEmpty()) { Cards revealed = new CardsImpl(); Card card = opponent.getHand().getRandom(game); diff --git a/Mage.Sets/src/mage/cards/p/PlaneswalkersMirth.java b/Mage.Sets/src/mage/cards/p/PlaneswalkersMirth.java index 4bfb0ea7183..30c55f2f6d1 100644 --- a/Mage.Sets/src/mage/cards/p/PlaneswalkersMirth.java +++ b/Mage.Sets/src/mage/cards/p/PlaneswalkersMirth.java @@ -56,7 +56,7 @@ class PlaneswalkersMirthEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); Player player = game.getPlayer(source.getControllerId()); if (opponent != null && player!= null && !opponent.getHand().isEmpty()) { Cards revealed = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/p/PlaneswalkersMischief.java b/Mage.Sets/src/mage/cards/p/PlaneswalkersMischief.java index ee97947fcaf..0b9d61d0add 100644 --- a/Mage.Sets/src/mage/cards/p/PlaneswalkersMischief.java +++ b/Mage.Sets/src/mage/cards/p/PlaneswalkersMischief.java @@ -115,7 +115,7 @@ class PlaneswalkersMischiefCastFromExileEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (targetPointer.getTargets(game, source).contains(objectId) + if (getTargetPointer().getTargets(game, source).contains(objectId) && game.getState().getZone(objectId) == Zone.EXILED) { Player player = game.getPlayer(source.getControllerId()); Card card = game.getCard(objectId); diff --git a/Mage.Sets/src/mage/cards/p/PolygraphOrb.java b/Mage.Sets/src/mage/cards/p/PolygraphOrb.java new file mode 100644 index 00000000000..506beae7271 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PolygraphOrb.java @@ -0,0 +1,133 @@ +package mage.cards.p; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetDiscard; +import mage.target.common.TargetSacrifice; + +/** + * + * @author DominionSpy + */ +public final class PolygraphOrb extends CardImpl { + + public PolygraphOrb(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{B}"); + + // When Polygraph Orb enters the battlefield, look at the top four cards of your library. + // Put two of them into your hand and the rest into your graveyard. You lose 2 life. + Ability ability = new EntersBattlefieldTriggeredAbility( + new LookLibraryAndPickControllerEffect( + 4, 2, PutCards.HAND, PutCards.GRAVEYARD)); + ability.addEffect(new LoseLifeSourceControllerEffect(2)); + this.addAbility(ability); + + // {2}, {T}, Collect evidence 3: Each opponent loses 3 life unless they discard a card or sacrifice a creature. + ability = new SimpleActivatedAbility(new PolygraphOrbEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new CollectEvidenceCost(3)); + this.addAbility(ability); + } + + private PolygraphOrb(final PolygraphOrb card) { + super(card); + } + + @Override + public PolygraphOrb copy() { + return new PolygraphOrb(this); + } +} + +class PolygraphOrbEffect extends OneShotEffect { + + PolygraphOrbEffect() { + super(Outcome.Benefit); + staticText = "Each opponent loses 3 life unless they discard a card or sacrifice a creature"; + } + + private PolygraphOrbEffect(final PolygraphOrbEffect effect) { + super(effect); + } + + @Override + public PolygraphOrbEffect copy() { + return new PolygraphOrbEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Map chosenCards = new HashMap<>(); + for (UUID opponentId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + + TargetDiscard targetDiscard = new TargetDiscard(0, 1, StaticFilters.FILTER_CARD_A, opponentId); + targetDiscard.withChooseHint("otherwise you have to sacrifice a creature or lose 3 life"); + if (opponent.choose(Outcome.PreventDamage, targetDiscard, source, game)) { + chosenCards.put(opponentId, game.getCard(targetDiscard.getFirstTarget())); + continue; + } + + TargetSacrifice targetSacrifice = new TargetSacrifice(0, 1, StaticFilters.FILTER_CONTROLLED_CREATURE); + targetSacrifice.withChooseHint("otherwise you lose 3 life"); + if (opponent.choose(Outcome.PreventDamage, targetSacrifice, source, game)) { + chosenCards.put(opponentId, game.getCard(targetSacrifice.getFirstTarget())); + continue; + } + + chosenCards.put(opponentId, null); + } + for (Map.Entry entry : chosenCards.entrySet()) { + Player opponent = game.getPlayer(entry.getKey()); + if (opponent == null) { + continue; + } + if (entry.getValue() != null) { + Card card = entry.getValue(); + Zone zone = game.getState().getZone(card.getId()); + if (Zone.HAND.match(zone)) { + opponent.discard(card, false, source, game); + continue; + } else if (Zone.BATTLEFIELD.match(zone)) { + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null) { + permanent.sacrifice(source, game); + continue; + } + } + } + opponent.loseLife(3, game, source, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PortRazer.java b/Mage.Sets/src/mage/cards/p/PortRazer.java index efd905191f2..e1553eaa2e6 100644 --- a/Mage.Sets/src/mage/cards/p/PortRazer.java +++ b/Mage.Sets/src/mage/cards/p/PortRazer.java @@ -41,8 +41,7 @@ public final class PortRazer extends CardImpl { "untap each creature you control" ), false ); - ability.addEffect(new AdditionalCombatPhaseEffect() - .setText("After this combat phase, there is an additional combat phase.")); + ability.addEffect(new AdditionalCombatPhaseEffect()); this.addAbility(ability); // Port Razer can't attack a player it has already attacked this turn. diff --git a/Mage.Sets/src/mage/cards/p/PortalToPhyrexia.java b/Mage.Sets/src/mage/cards/p/PortalToPhyrexia.java index f5da2256ccb..6cb416c830b 100644 --- a/Mage.Sets/src/mage/cards/p/PortalToPhyrexia.java +++ b/Mage.Sets/src/mage/cards/p/PortalToPhyrexia.java @@ -23,8 +23,6 @@ import java.util.UUID; */ public final class PortalToPhyrexia extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public PortalToPhyrexia(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{9}"); @@ -38,7 +36,7 @@ public final class PortalToPhyrexia extends CardImpl { new ReturnFromGraveyardToBattlefieldTargetEffect(), TargetController.YOU, false ); ability.addEffect(new AddCreatureTypeAdditionEffect(SubType.PHYREXIAN, false)); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/Portcullis.java b/Mage.Sets/src/mage/cards/p/Portcullis.java index 7497944d0f0..60b3fb061ab 100644 --- a/Mage.Sets/src/mage/cards/p/Portcullis.java +++ b/Mage.Sets/src/mage/cards/p/Portcullis.java @@ -97,10 +97,9 @@ class PortcullisExileEffect extends OneShotEffect { && controller != null) { UUID exileZoneId = CardUtil.getExileZoneId(game, creatureToExile.getId(), creatureToExile.getZoneChangeCounter(game)); controller.moveCardsToExile(creatureToExile, source, game, true, exileZoneId, portcullis.getName()); - FixedTarget fixedTarget = new FixedTarget(portcullis, game); Effect returnEffect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false); returnEffect.setTargetPointer(new FixedTarget(creatureToExile.getId(), game.getState().getZoneChangeCounter(creatureToExile.getId()))); - DelayedTriggeredAbility delayedAbility = new PortcullisReturnToBattlefieldTriggeredAbility(fixedTarget, returnEffect); + DelayedTriggeredAbility delayedAbility = new PortcullisReturnToBattlefieldTriggeredAbility(new FixedTarget(portcullis, game), returnEffect); game.addDelayedTriggeredAbility(delayedAbility, source); } return true; @@ -109,7 +108,7 @@ class PortcullisExileEffect extends OneShotEffect { class PortcullisReturnToBattlefieldTriggeredAbility extends DelayedTriggeredAbility { - protected FixedTarget fixedTarget; + protected final FixedTarget fixedTarget; public PortcullisReturnToBattlefieldTriggeredAbility(FixedTarget fixedTarget, Effect effect) { super(effect, Duration.OneUse); @@ -118,7 +117,7 @@ class PortcullisReturnToBattlefieldTriggeredAbility extends DelayedTriggeredAbil private PortcullisReturnToBattlefieldTriggeredAbility(final PortcullisReturnToBattlefieldTriggeredAbility ability) { super(ability); - this.fixedTarget = ability.fixedTarget; + this.fixedTarget = ability.fixedTarget.copy(); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PossibilityStorm.java b/Mage.Sets/src/mage/cards/p/PossibilityStorm.java index 2fe84f83490..7a17a85ea6e 100644 --- a/Mage.Sets/src/mage/cards/p/PossibilityStorm.java +++ b/Mage.Sets/src/mage/cards/p/PossibilityStorm.java @@ -100,10 +100,10 @@ class PossibilityStormEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); boolean noLongerOnStack = false; // spell was exiled already by another effect, for example NivMagus Elemental if (spell == null) { - spell = ((Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK)); + spell = ((Spell) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK)); noLongerOnStack = true; } if (spell == null) { diff --git a/Mage.Sets/src/mage/cards/p/PowderGanger.java b/Mage.Sets/src/mage/cards/p/PowderGanger.java new file mode 100644 index 00000000000..a22a0e82ea7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PowderGanger.java @@ -0,0 +1,46 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.SquadAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetArtifactPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PowderGanger extends CardImpl { + + public PowderGanger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Squad {2} + this.addAbility(new SquadAbility()); + + // When Powder Ganger enters the battlefield, destroy up to one target artifact. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetArtifactPermanent(0, 1)); + this.addAbility(ability); + } + + private PowderGanger(final PowderGanger card) { + super(card); + } + + @Override + public PowderGanger copy() { + return new PowderGanger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PowerSink.java b/Mage.Sets/src/mage/cards/p/PowerSink.java index dd32f3187d0..354870701b1 100644 --- a/Mage.Sets/src/mage/cards/p/PowerSink.java +++ b/Mage.Sets/src/mage/cards/p/PowerSink.java @@ -62,7 +62,7 @@ class PowerSinkCounterUnlessPaysEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/p/PowerSurge.java b/Mage.Sets/src/mage/cards/p/PowerSurge.java index c8173971c7a..45e4bbf0421 100644 --- a/Mage.Sets/src/mage/cards/p/PowerSurge.java +++ b/Mage.Sets/src/mage/cards/p/PowerSurge.java @@ -51,7 +51,7 @@ class PowerSurgeDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { PowerSurgeWatcher watcher = game.getState().getWatcher(PowerSurgeWatcher.class); if (watcher != null) { diff --git a/Mage.Sets/src/mage/cards/p/Preacher.java b/Mage.Sets/src/mage/cards/p/Preacher.java index 7cfe53fa1c9..03b109e8d13 100644 --- a/Mage.Sets/src/mage/cards/p/Preacher.java +++ b/Mage.Sets/src/mage/cards/p/Preacher.java @@ -12,7 +12,7 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -38,7 +38,7 @@ public final class Preacher extends CardImpl { // {T}: Gain control of target creature of an opponent's choice that they control for as long as Preacher remains tapped. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreacherEffect(), new TapSourceCost()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PrecognitionField.java b/Mage.Sets/src/mage/cards/p/PrecognitionField.java index fda488a38f6..1641ba976c8 100644 --- a/Mage.Sets/src/mage/cards/p/PrecognitionField.java +++ b/Mage.Sets/src/mage/cards/p/PrecognitionField.java @@ -1,23 +1,17 @@ package mage.cards.p; -import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; import java.util.UUID; @@ -45,7 +39,7 @@ public final class PrecognitionField extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // {3}: Exile the top card of your library. - this.addAbility(new SimpleActivatedAbility(new PrecognitionFieldExileEffect(), new GenericManaCost(3))); + this.addAbility(new SimpleActivatedAbility(new ExileCardsFromTopOfLibraryControllerEffect(1), new GenericManaCost(3))); } private PrecognitionField(final PrecognitionField card) { @@ -57,33 +51,3 @@ public final class PrecognitionField extends CardImpl { return new PrecognitionField(this); } } - -class PrecognitionFieldExileEffect extends OneShotEffect { - - PrecognitionFieldExileEffect() { - super(Outcome.Benefit); - staticText = "exile the top card of your library"; - } - - private PrecognitionFieldExileEffect(final PrecognitionFieldExileEffect effect) { - super(effect); - } - - @Override - public PrecognitionFieldExileEffect copy() { - return new PrecognitionFieldExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - controller.moveCards(card, Zone.EXILED, source, game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/p/PredatorsHour.java b/Mage.Sets/src/mage/cards/p/PredatorsHour.java index 040665b84f8..9fd682a76af 100644 --- a/Mage.Sets/src/mage/cards/p/PredatorsHour.java +++ b/Mage.Sets/src/mage/cards/p/PredatorsHour.java @@ -88,7 +88,7 @@ class PredatorsHourEffect extends OneShotEffect { return false; } - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java b/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java index 3690c5fb798..9789b1a2023 100644 --- a/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java +++ b/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java @@ -21,7 +21,6 @@ import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TokenPredicate; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetNonlandPermanent; /** @@ -32,7 +31,7 @@ public final class PrestonTheVanisher extends CardImpl { private static final FilterControlledCreaturePermanent triggerFilter = new FilterControlledCreaturePermanent( "another nontoken creature"); - private static final FilterControlledPermanent activeCostFilter = new FilterControlledCreaturePermanent( + private static final FilterControlledPermanent activeCostFilter = new FilterControlledPermanent( SubType.ILLUSION, "Illusions"); static { diff --git a/Mage.Sets/src/mage/cards/p/PresumedDead.java b/Mage.Sets/src/mage/cards/p/PresumedDead.java new file mode 100644 index 00000000000..0018d6ecc0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PresumedDead.java @@ -0,0 +1,81 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class PresumedDead extends CardImpl { + + public PresumedDead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Until end of turn, target creature gets +2/+0 and gains "When this creature dies, return it to the battlefield under its owner's control and suspect it." + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new BoostTargetEffect(2, 0, Duration.EndOfTurn) + .setText("Until end of turn, target creature gets +2/+0")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(new DiesSourceTriggeredAbility(new PresumedDeadEffect())) + .setText("and gains \"When this creature dies, return it to the battlefield under its owner's control and suspect it.\"")); + + } + + private PresumedDead(final PresumedDead card) { + super(card); + } + + @Override + public PresumedDead copy() { + return new PresumedDead(this); + } +} + +class PresumedDeadEffect extends OneShotEffect { + + PresumedDeadEffect() { + super(Outcome.Benefit); + staticText = "return it to the battlefield under its owner's control and suspect it"; + } + + private PresumedDeadEffect(final PresumedDeadEffect effect) { + super(effect); + } + + @Override + public PresumedDeadEffect copy() { + return new PresumedDeadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(source.getSourceId()); + if (controller == null || card == null) { + return false; + } + controller.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null); + game.getState().processAction(game); + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null) { + permanent.setSuspected(true, game, source); + } + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/p/PriceOfKnowledge.java b/Mage.Sets/src/mage/cards/p/PriceOfKnowledge.java index 36367d74832..53dee313077 100644 --- a/Mage.Sets/src/mage/cards/p/PriceOfKnowledge.java +++ b/Mage.Sets/src/mage/cards/p/PriceOfKnowledge.java @@ -56,7 +56,7 @@ class PriceOfKnowledgeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { int xValue = targetPlayer.getHand().size(); if (xValue > 0) { diff --git a/Mage.Sets/src/mage/cards/p/PrideSovereign.java b/Mage.Sets/src/mage/cards/p/PrideSovereign.java index f72b39e8438..0cc53452844 100644 --- a/Mage.Sets/src/mage/cards/p/PrideSovereign.java +++ b/Mage.Sets/src/mage/cards/p/PrideSovereign.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -19,7 +18,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.permanent.token.CatToken2; @@ -29,6 +28,11 @@ import mage.game.permanent.token.CatToken2; */ public final class PrideSovereign extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CAT, "other Cat you control"); + static { + filter.add(AnotherPredicate.instance); + } + public PrideSovereign(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); @@ -37,8 +41,6 @@ public final class PrideSovereign extends CardImpl { this.toughness = new MageInt(2); // Pride Sovereign gets +1/+1 for each other Cat you control. - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.CAT, "other Cat you control"); - filter.add(AnotherPredicate.instance); DynamicValue otherCats = new PermanentsOnBattlefieldCount(filter); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostSourceEffect(otherCats, otherCats, Duration.WhileOnBattlefield))); // {W}, {t}, Exert Pride Sovereign: Create two 1/1 white Cat creature tokens with lifelink. diff --git a/Mage.Sets/src/mage/cards/p/PrimalAmulet.java b/Mage.Sets/src/mage/cards/p/PrimalAmulet.java index 131fd9103f8..5dbd7ce2ce2 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalAmulet.java +++ b/Mage.Sets/src/mage/cards/p/PrimalAmulet.java @@ -1,4 +1,3 @@ - package mage.cards.p; import mage.abilities.Ability; @@ -15,7 +14,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -46,7 +45,7 @@ public final class PrimalAmulet extends CardImpl { // Whenever you cast an instant or sorcery spell, put a charge counter on Primal Amulet. Then if there are four or more charge counters on it, you may remove those counters and transform it. this.addAbility(new TransformAbility()); - this.addAbility(new SpellCastControllerTriggeredAbility(new PrimalAmuletEffect(), new FilterInstantOrSorcerySpell(), false)); + this.addAbility(new SpellCastControllerTriggeredAbility(new PrimalAmuletEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false)); } private PrimalAmulet(final PrimalAmulet card) { diff --git a/Mage.Sets/src/mage/cards/p/PrimalOrder.java b/Mage.Sets/src/mage/cards/p/PrimalOrder.java index 46f384bf22a..cfdf4d983a2 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalOrder.java +++ b/Mage.Sets/src/mage/cards/p/PrimalOrder.java @@ -53,9 +53,9 @@ class PrimalOrderDamageTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - int damage = game.getBattlefield().getAllActivePermanents(filter, targetPointer.getFirst(game, source), game).size(); + int damage = game.getBattlefield().getAllActivePermanents(filter, getTargetPointer().getFirst(game, source), game).size(); player.damage(damage, source.getSourceId(), source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/p/PrimordialMist.java b/Mage.Sets/src/mage/cards/p/PrimordialMist.java index f373b738209..49b4fc05ee4 100644 --- a/Mage.Sets/src/mage/cards/p/PrimordialMist.java +++ b/Mage.Sets/src/mage/cards/p/PrimordialMist.java @@ -68,7 +68,7 @@ class PrimordialMistCost extends CostImpl { public PrimordialMistCost(TargetPermanent target) { this.target = target; - this.text = "Exile a face-down permanent you control face-up"; + this.text = "Exile a face-down permanent you control face up"; } private PrimordialMistCost(final PrimordialMistCost cost) { @@ -121,7 +121,7 @@ class PrimordialMistCastFromExileEffect extends AsThoughEffectImpl { PrimordialMistCastFromExileEffect() { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); - staticText = "Exile a face-down permanent you control face up: You may play that card this turn."; + staticText = "You may play that card this turn."; } private PrimordialMistCastFromExileEffect(final PrimordialMistCastFromExileEffect effect) { @@ -141,7 +141,7 @@ class PrimordialMistCastFromExileEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { return source.isControlledBy(affectedControllerId) - && (game.getCard(targetPointer.getFirst(game, source)) != null) - && objectId == targetPointer.getFirst(game, source); + && (game.getCard(getTargetPointer().getFirst(game, source)) != null) + && objectId == getTargetPointer().getFirst(game, source); } } diff --git a/Mage.Sets/src/mage/cards/p/PrinceOfThralls.java b/Mage.Sets/src/mage/cards/p/PrinceOfThralls.java index 426180bbd39..3fd32f3d9e5 100644 --- a/Mage.Sets/src/mage/cards/p/PrinceOfThralls.java +++ b/Mage.Sets/src/mage/cards/p/PrinceOfThralls.java @@ -106,8 +106,8 @@ class PrinceOfThrallsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); - Permanent permanent = (Permanent) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.BATTLEFIELD); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + Permanent permanent = (Permanent) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.BATTLEFIELD); if (controller != null && card != null && permanent != null) { Player opponent = game.getPlayer(permanent.getControllerId()); if (opponent != null) { diff --git a/Mage.Sets/src/mage/cards/p/PrintlifterOoze.java b/Mage.Sets/src/mage/cards/p/PrintlifterOoze.java new file mode 100644 index 00000000000..deb1ce691cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrintlifterOoze.java @@ -0,0 +1,89 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TurnedFaceUpAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanentThisOrAnother; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.OozeTrampleToken; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class PrintlifterOoze extends CardImpl { + + private static final FilterPermanentThisOrAnother filter = new FilterPermanentThisOrAnother(StaticFilters.FILTER_CONTROLLED_CREATURE, true); + + public PrintlifterOoze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + this.subtype.add(SubType.OOZE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Printlifter Ooze or another creature you control is turned face up, create a 0/0 green Ooze creature token with trample. + // The token enters the battlefield with X +1/+1 counters on it, where X is the number of other creatures you control. + this.addAbility(new TurnedFaceUpAllTriggeredAbility( + new PrintlifterOozeEffect(), filter + )); + + // Disguise {3}{G} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{3}{G}"))); + } + + private PrintlifterOoze(final PrintlifterOoze card) { + super(card); + } + + @Override + public PrintlifterOoze copy() { + return new PrintlifterOoze(this); + } +} + +class PrintlifterOozeEffect extends OneShotEffect { + + PrintlifterOozeEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "create a 0/0 green Ooze creature token with trample. The token enters the battlefield " + + "with X +1/+1 counters on it, where X is the number of other creatures you control"; + } + + private PrintlifterOozeEffect(final PrintlifterOozeEffect effect) { + super(effect); + } + + @Override + public PrintlifterOozeEffect copy() { + return new PrintlifterOozeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID controller = source.getControllerId(); + if (controller == null) { + return false; + } + int xVal = game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURE, controller, source, game); + Effect effect = new CreateTokenEffect(new OozeTrampleToken()).entersWithCounters(CounterType.P1P1, StaticValue.get(xVal)); + return effect.apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrismaticCircle.java b/Mage.Sets/src/mage/cards/p/PrismaticCircle.java index 5a0a12f0598..91be2dabb3c 100644 --- a/Mage.Sets/src/mage/cards/p/PrismaticCircle.java +++ b/Mage.Sets/src/mage/cards/p/PrismaticCircle.java @@ -58,9 +58,9 @@ class PrismaticCircleEffect extends PreventNextDamageFromChosenSourceToYouEffect @Override public void init(Ability source, Game game) { + super.init(source, game); FilterObject filter = targetSource.getFilter(); filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color"))); - super.init(source, game); } private PrismaticCircleEffect(final PrismaticCircleEffect effect) { diff --git a/Mage.Sets/src/mage/cards/p/ProfaneProcession.java b/Mage.Sets/src/mage/cards/p/ProfaneProcession.java index f6831274a1a..a3ccc5f500c 100644 --- a/Mage.Sets/src/mage/cards/p/ProfaneProcession.java +++ b/Mage.Sets/src/mage/cards/p/ProfaneProcession.java @@ -74,7 +74,7 @@ class ProfaneProcessionEffect extends OneShotEffect { UUID exileId = CardUtil.getCardExileZoneId(game, source); MageObject sourceObject = source.getSourceObject(game); if (controller != null && exileId != null && sourceObject != null) { - new ExileTargetEffect(exileId, sourceObject.getIdName()).setTargetPointer(targetPointer).apply(game, source); + new ExileTargetEffect(exileId, sourceObject.getIdName()).setTargetPointer(this.getTargetPointer().copy()).apply(game, source); game.getState().processAction(game); ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone != null && exileZone.size() > 2) { diff --git a/Mage.Sets/src/mage/cards/p/ProftsEideticMemory.java b/Mage.Sets/src/mage/cards/p/ProftsEideticMemory.java new file mode 100644 index 00000000000..785225b7d5d --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProftsEideticMemory.java @@ -0,0 +1,69 @@ +package mage.cards.p; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.DrewTwoOrMoreCardsCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author DominionSpy + */ +public final class ProftsEideticMemory extends CardImpl { + + public ProftsEideticMemory(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Proft's Eidetic Memory enters the battlefield, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + + // You have no maximum hand size. + this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect( + Integer.MAX_VALUE, Duration.WhileOnBattlefield, + MaximumHandSizeControllerEffect.HandSizeModification.SET))); + + // At the beginning of combat on your turn, if you've drawn more than one card this turn, + // put X +1/+1 counters on target creature you control, + // where X is the number of cards you've drawn this turn minus one. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new BeginningOfCombatTriggeredAbility( + new AddCountersTargetEffect( + CounterType.P1P1.createInstance(), + new IntPlusDynamicValue(-1, CardsDrawnThisTurnDynamicValue.instance)), + TargetController.YOU, false), + DrewTwoOrMoreCardsCondition.instance, + "At the beginning of combat on your turn, if you've drawn more than one card this turn, " + + "put X +1/+1 counters on target creature you control, " + + "where X is the number of cards you've drawn this turn minus one"); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private ProftsEideticMemory(final ProftsEideticMemory card) { + super(card); + } + + @Override + public ProftsEideticMemory copy() { + return new ProftsEideticMemory(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/ProgenitorsIcon.java b/Mage.Sets/src/mage/cards/p/ProgenitorsIcon.java index 56823eb3f4d..762037cfc4a 100644 --- a/Mage.Sets/src/mage/cards/p/ProgenitorsIcon.java +++ b/Mage.Sets/src/mage/cards/p/ProgenitorsIcon.java @@ -64,6 +64,7 @@ class ProgenitorsIconAsThoughEffect extends AsThoughEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); SubType subType = ChooseCreatureTypeEffect.getChosenCreatureType(source.getSourceId(), game); ProgenitorsIconWatcher.addPlayer(source.getControllerId(), subType, game); } diff --git a/Mage.Sets/src/mage/cards/p/ProteanHydra.java b/Mage.Sets/src/mage/cards/p/ProteanHydra.java index 5c21bfbac93..335e9602a78 100644 --- a/Mage.Sets/src/mage/cards/p/ProteanHydra.java +++ b/Mage.Sets/src/mage/cards/p/ProteanHydra.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -53,67 +52,68 @@ public final class ProteanHydra extends CardImpl { return new ProteanHydra(this); } - class ProteanHydraAbility extends TriggeredAbilityImpl { - - public ProteanHydraAbility() { - super(Zone.BATTLEFIELD, new CreateDelayedTriggeredAbilityEffect(new ProteanHydraDelayedTriggeredAbility()), false); - } - - private ProteanHydraAbility(final ProteanHydraAbility ability) { - super(ability); - } - - @Override - public ProteanHydraAbility copy() { - return new ProteanHydraAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COUNTER_REMOVED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getData().equals(CounterType.P1P1.getName()) && event.getTargetId().equals(this.getSourceId()); - } - - @Override - public String getRule() { - return "Whenever a +1/+1 counter is removed from {this}, put two +1/+1 counters on it at the beginning of the next end step."; - } - - } - - static class ProteanHydraDelayedTriggeredAbility extends DelayedTriggeredAbility { - - public ProteanHydraDelayedTriggeredAbility() { - super(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2))); - } - - private ProteanHydraDelayedTriggeredAbility(final ProteanHydraDelayedTriggeredAbility ability) { - super(ability); - } - - @Override - public ProteanHydraDelayedTriggeredAbility copy() { - return new ProteanHydraDelayedTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return true; - } - - @Override - public String getRule() { - return "Put two +1/+1 counters on {this} at the beginning of the next end step"; - } - - } +} + +class ProteanHydraAbility extends TriggeredAbilityImpl { + + ProteanHydraAbility() { + super(Zone.BATTLEFIELD, new CreateDelayedTriggeredAbilityEffect(new ProteanHydraDelayedTriggeredAbility()), false); + } + + private ProteanHydraAbility(final ProteanHydraAbility ability) { + super(ability); + } + + @Override + public ProteanHydraAbility copy() { + return new ProteanHydraAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTER_REMOVED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getData().equals(CounterType.P1P1.getName()) && event.getTargetId().equals(this.getSourceId()); + } + + @Override + public String getRule() { + return "Whenever a +1/+1 counter is removed from {this}, put two +1/+1 counters on it at the beginning of the next end step."; + } + +} + +class ProteanHydraDelayedTriggeredAbility extends DelayedTriggeredAbility { + + ProteanHydraDelayedTriggeredAbility() { + super(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2))); + } + + private ProteanHydraDelayedTriggeredAbility(final ProteanHydraDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public ProteanHydraDelayedTriggeredAbility copy() { + return new ProteanHydraDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return true; + } + + @Override + public String getRule() { + return "Put two +1/+1 counters on {this} at the beginning of the next end step"; + } + } diff --git a/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java b/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java index 9d4c9e49ca6..215d66ae3d5 100644 --- a/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java +++ b/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java @@ -75,6 +75,7 @@ class ProtectiveSphereEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); target.withNotTarget(true); target.setRequired(false); Player controller = game.getPlayer(source.getControllerId()); @@ -89,7 +90,6 @@ class ProtectiveSphereEffect extends PreventionEffectImpl { + source.getManaCostsToPay().getUsedManaToPay()), game); } this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); - super.init(source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PsychicAllergy.java b/Mage.Sets/src/mage/cards/p/PsychicAllergy.java index 587d2d34196..7364504e1af 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicAllergy.java +++ b/Mage.Sets/src/mage/cards/p/PsychicAllergy.java @@ -73,7 +73,7 @@ class PsychicAllergyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { FilterPermanent filter = new FilterPermanent(); filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color"))); diff --git a/Mage.Sets/src/mage/cards/p/PsychicFrog.java b/Mage.Sets/src/mage/cards/p/PsychicFrog.java new file mode 100644 index 00000000000..9c020c12f65 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PsychicFrog.java @@ -0,0 +1,59 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PsychicFrog extends CardImpl { + + public PsychicFrog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.subtype.add(SubType.FROG); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Whenever Psychic Frog deals combat damage to a player or planeswalker, draw a card. + this.addAbility(new DealsCombatDamageToAPlayerOrPlaneswalkerTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + )); + + // Discard a card: Put a +1/+1 counter on Psychic Frog. + this.addAbility(new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new DiscardCardCost() + )); + + // Exile three cards from your graveyard: Psychic Frog gains flying until end of turn. + this.addAbility(new SimpleActivatedAbility( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), + new ExileFromGraveCost(new TargetCardInYourGraveyard(3)) + )); + } + + private PsychicFrog(final PsychicFrog card) { + super(card); + } + + @Override + public PsychicFrog copy() { + return new PsychicFrog(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java b/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java index ac0282545b9..b0d420eb465 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java +++ b/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java @@ -67,7 +67,7 @@ class PsychicIntrusionExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (opponent != null && sourceObject != null) { opponent.revealCards(sourceObject.getName(), opponent.getHand(), game); diff --git a/Mage.Sets/src/mage/cards/p/PsychicMiasma.java b/Mage.Sets/src/mage/cards/p/PsychicMiasma.java index d824fa66c07..1890745fcfe 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicMiasma.java +++ b/Mage.Sets/src/mage/cards/p/PsychicMiasma.java @@ -52,7 +52,7 @@ class PsychicMiasmaEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Card discardedCard = player.discardOne(false, false, source, game); if (discardedCard != null && discardedCard.isLand(game)) { diff --git a/Mage.Sets/src/mage/cards/p/PsychicStrike.java b/Mage.Sets/src/mage/cards/p/PsychicStrike.java index 208c50fe765..c82aec9931f 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicStrike.java +++ b/Mage.Sets/src/mage/cards/p/PsychicStrike.java @@ -55,7 +55,7 @@ class PsychicStrikeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean countered = false; - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (game.getStack().counter(source.getFirstTarget(), source, game)) { countered = true; } diff --git a/Mage.Sets/src/mage/cards/p/PsychicTheft.java b/Mage.Sets/src/mage/cards/p/PsychicTheft.java index d2017792631..20b6179dc37 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicTheft.java +++ b/Mage.Sets/src/mage/cards/p/PsychicTheft.java @@ -69,7 +69,7 @@ class PsychicTheftEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/p/PsychoticEpisode.java b/Mage.Sets/src/mage/cards/p/PsychoticEpisode.java index ed9dc2a92be..5cdee0e6bd3 100644 --- a/Mage.Sets/src/mage/cards/p/PsychoticEpisode.java +++ b/Mage.Sets/src/mage/cards/p/PsychoticEpisode.java @@ -60,7 +60,7 @@ class PsychoticEpisodeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (player != null && controller != null && sourceObject != null) { diff --git a/Mage.Sets/src/mage/cards/p/PsychotropeThallid.java b/Mage.Sets/src/mage/cards/p/PsychotropeThallid.java index 5f561b3acae..3126a863ce3 100644 --- a/Mage.Sets/src/mage/cards/p/PsychotropeThallid.java +++ b/Mage.Sets/src/mage/cards/p/PsychotropeThallid.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -19,9 +18,8 @@ import mage.constants.SubType; import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -29,10 +27,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class PsychotropeThallid extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Saproling"); - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SAPROLING, "a Saproling"); public PsychotropeThallid(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/p/PuppetConjurer.java b/Mage.Sets/src/mage/cards/p/PuppetConjurer.java index eca9ccfa9ee..47d5d1f2e83 100644 --- a/Mage.Sets/src/mage/cards/p/PuppetConjurer.java +++ b/Mage.Sets/src/mage/cards/p/PuppetConjurer.java @@ -16,7 +16,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.HomunculusToken; /** @@ -25,11 +25,7 @@ import mage.game.permanent.token.HomunculusToken; */ public final class PuppetConjurer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Homunculus"); - - static { - filter.add(SubType.HOMUNCULUS.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HOMUNCULUS, "Homunculus"); public PuppetConjurer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{B}"); diff --git a/Mage.Sets/src/mage/cards/p/PureReflection.java b/Mage.Sets/src/mage/cards/p/PureReflection.java index 5736f6bf006..c4c7fd8bd2b 100644 --- a/Mage.Sets/src/mage/cards/p/PureReflection.java +++ b/Mage.Sets/src/mage/cards/p/PureReflection.java @@ -37,50 +37,49 @@ public final class PureReflection extends CardImpl { public PureReflection copy() { return new PureReflection(this); } +} - private class PureReflectionEffect extends OneShotEffect { +class PureReflectionEffect extends OneShotEffect { - PureReflectionEffect() { - super(Outcome.Benefit); - staticText = "destroy all Reflections. Then that player creates an X/X white Reflection creature token, where X is the mana value of that spell."; + PureReflectionEffect() { + super(Outcome.Benefit); + staticText = "destroy all Reflections. Then that player creates an X/X white Reflection creature token, where X is the mana value of that spell."; + } + + private PureReflectionEffect(final PureReflectionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + if (controller == null || game.getPermanentOrLKIBattlefield(source.getSourceId()) == null) { + return false; } - private PureReflectionEffect(final PureReflectionEffect effect) { - super(effect); + Spell spell = game.getSpellOrLKIStack(this.getTargetPointer().getFirst(game, source)); + + if (spell == null) { + return false; } - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); + // destroy all Reflections + FilterPermanent filter = new FilterPermanent("Reflections"); + filter.add(SubType.REFLECTION.getPredicate()); + game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game) + .forEach(permanent -> permanent.destroy(source, game,false)); + game.getState().processAction(game); - if (controller == null || game.getPermanentOrLKIBattlefield(source.getSourceId()) == null) { - return false; - } + // Then that player creates an X/X white Reflection creature token, where X is the converted mana cost of that spell. + ReflectionPureToken token = new ReflectionPureToken(spell.getManaValue()); + token.putOntoBattlefield(1, game, source, spell.getControllerId()); - Spell spell = game.getSpellOrLKIStack(this.getTargetPointer().getFirst(game, source)); + return true; + } - if (spell == null) { - return false; - } - - // destroy all Reflections - FilterPermanent filter = new FilterPermanent("Reflections"); - filter.add(SubType.REFLECTION.getPredicate()); - game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game).forEach((permanent) -> { - permanent.destroy(source, game,false); - }); - game.getState().processAction(game); - - // Then that player creates an X/X white Reflection creature token, where X is the converted mana cost of that spell. - ReflectionPureToken token = new ReflectionPureToken(spell.getManaValue()); - token.putOntoBattlefield(1, game, source, spell.getControllerId()); - - return true; - } - - @Override - public PureReflectionEffect copy() { - return new PureReflectionEffect(this); - } + @Override + public PureReflectionEffect copy() { + return new PureReflectionEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/p/PushPull.java b/Mage.Sets/src/mage/cards/p/PushPull.java new file mode 100644 index 00000000000..7c829f6960f --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PushPull.java @@ -0,0 +1,125 @@ +package mage.cards.p; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardSetInfo; +import mage.cards.SplitCard; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SpellAbilityType; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInASingleGraveyard; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author DominionSpy + */ +public final class PushPull extends SplitCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public PushPull(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W/B}", "{4}{B/R}{B/R}", SpellAbilityType.SPLIT); + + // Push + // Destroy target tapped creature. + getLeftHalfCard().getSpellAbility().addEffect(new DestroyTargetEffect()); + getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + + // Pull + // Put up to two target creature cards from a single graveyard onto the battlefield under your control. They gain haste until end of turn. Sacrifice them at the beginning of the next end step. + getRightHalfCard().getSpellAbility().addEffect(new PullEffect()); + getRightHalfCard().getSpellAbility().addTarget(new TargetCardInASingleGraveyard(0, 2, + new FilterCreatureCard("up to two creature cards"))); + + } + + private PushPull(final PushPull card) { + super(card); + } + + @Override + public PushPull copy() { + return new PushPull(this); + } +} + +class PullEffect extends OneShotEffect { + + PullEffect() { + super(Outcome.PutCardInPlay); + staticText = "Put up to two target creature cards from a single graveyard onto the battlefield under your control. " + + "They gain haste until end of turn. Sacrifice them at the beginning of the next end step."; + } + + private PullEffect(final PullEffect effect) { + super(effect); + } + + @Override + public PullEffect copy() { + return new PullEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Set cards = getTargetPointer().getTargets(game, source) + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (controller == null || cards.isEmpty() + || !controller.moveCards(cards, Zone.BATTLEFIELD, source, game)) { + return false; + } + + Set permanents = cards.stream() + .map(Card::getId) + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (permanents.isEmpty()) { + return false; + } + + permanents.forEach(permanent -> { + FixedTarget blueprintTarget = new FixedTarget(permanent, game); + + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance()); + effect.setTargetPointer(blueprintTarget.copy()); + game.addEffect(effect, source); + + Effect sacrificeEffect = new SacrificeTargetEffect("sacrifice " + permanent.getLogName(), controller.getId()); + sacrificeEffect.setTargetPointer(blueprintTarget.copy()); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source); + }); + + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/Pyramids.java b/Mage.Sets/src/mage/cards/p/Pyramids.java index a2576668942..24002b8bab7 100644 --- a/Mage.Sets/src/mage/cards/p/Pyramids.java +++ b/Mage.Sets/src/mage/cards/p/Pyramids.java @@ -89,7 +89,7 @@ class PyramidsEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } @@ -105,7 +105,7 @@ class PyramidsEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(targetPointer.getFirst(game, source)) && !this.used; + return event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && !this.used; } } diff --git a/Mage.Sets/src/mage/cards/p/PyrotechnicPerformer.java b/Mage.Sets/src/mage/cards/p/PyrotechnicPerformer.java new file mode 100644 index 00000000000..e4b55c6cc84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PyrotechnicPerformer.java @@ -0,0 +1,86 @@ +package mage.cards.p; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TurnedFaceUpAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanentThisOrAnother; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * @author Cguy7777 + */ +public final class PyrotechnicPerformer extends CardImpl { + + private static final FilterPermanentThisOrAnother filter = new FilterPermanentThisOrAnother(StaticFilters.FILTER_CONTROLLED_CREATURE, true); + + public PyrotechnicPerformer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.VIASHINO); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Disguise {R} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{R}"))); + + // Whenever Pyrotechnic Performer or another creature you control is turned face up, that creature deals damage equal to its power to each opponent. + this.addAbility(new TurnedFaceUpAllTriggeredAbility(new PyrotechnicPerformerEffect(), filter, true)); + } + + private PyrotechnicPerformer(final PyrotechnicPerformer card) { + super(card); + } + + @Override + public PyrotechnicPerformer copy() { + return new PyrotechnicPerformer(this); + } +} + +class PyrotechnicPerformerEffect extends OneShotEffect { + + PyrotechnicPerformerEffect() { + super(Outcome.Damage); + staticText = "that creature deals damage equal to its power to each opponent"; + } + + private PyrotechnicPerformerEffect(final PyrotechnicPerformerEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent turnedUpCreature = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); + if (turnedUpCreature == null) { + return false; + } + + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent == null) { + continue; + } + opponent.damage(turnedUpCreature.getPower().getValue(), turnedUpCreature.getId(), source, game); + } + return true; + } + + @Override + public PyrotechnicPerformerEffect copy() { + return new PyrotechnicPerformerEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PyrrhicRevival.java b/Mage.Sets/src/mage/cards/p/PyrrhicRevival.java index 44b8cc76c2b..0fba4931129 100644 --- a/Mage.Sets/src/mage/cards/p/PyrrhicRevival.java +++ b/Mage.Sets/src/mage/cards/p/PyrrhicRevival.java @@ -1,19 +1,17 @@ - package mage.cards.p; import mage.abilities.Ability; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; +import mage.counters.Counters; import mage.game.Game; import mage.players.Player; -import mage.target.targetpointer.FixedTargets; import java.util.Objects; import java.util.Set; @@ -65,7 +63,6 @@ class PyrrhicRevivalEffect extends OneShotEffect { if (controller == null) { return false; } - Set toBattlefield = game.getState().getPlayersInRange(source.getControllerId(), game) .stream() @@ -74,16 +71,12 @@ class PyrrhicRevivalEffect extends OneShotEffect { .flatMap(p -> p.getGraveyard().getCards(game).stream()) .filter(c -> c != null && c.isCreature(game)) .collect(Collectors.toSet()); - - Effect returnEffect = - new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect( - CounterType.M1M1.createInstance(), - true, - true); - - returnEffect.setTargetPointer(new FixedTargets(toBattlefield, game)); - returnEffect.apply(game, source); - + Counters counters = new Counters(); + counters.addCounter(CounterType.M1M1.createInstance()); + for (Card card : toBattlefield) { + game.setEnterWithCounters(card.getId(), counters.copy()); + } + controller.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game, false, false, true, null); return true; } } diff --git a/Mage.Sets/src/mage/cards/q/QarsiDeceiver.java b/Mage.Sets/src/mage/cards/q/QarsiDeceiver.java index 086c12f658e..f129a8bd717 100644 --- a/Mage.Sets/src/mage/cards/q/QarsiDeceiver.java +++ b/Mage.Sets/src/mage/cards/q/QarsiDeceiver.java @@ -1,12 +1,10 @@ - package mage.cards.q; -import java.util.UUID; import mage.ConditionalMana; import mage.MageInt; -import mage.MageObject; import mage.Mana; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.common.TurnFaceUpAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.common.TapSourceCost; @@ -17,7 +15,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.game.Game; -import mage.game.stack.Spell; +import mage.game.permanent.Permanent; + +import java.util.UUID; /** * @@ -71,14 +71,17 @@ class QarsiDeceiverManaCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - MageObject object = game.getObject(source); - if (object instanceof Spell) { - if (((Spell) object).isFaceDown(game)) { - return true; - } + if (source instanceof SpellAbility) { + return ((SpellAbility) source).getSpellAbilityCastMode().isFaceDown() + && ((SpellAbility) source).getCharacteristics(game).isCreature(game); } + // morph cost or turn manifest creature face up if (source instanceof TurnFaceUpAbility) { - return true; + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + return false; + } + return permanent.isManifested() || permanent.isMorphed(); } return false; } diff --git a/Mage.Sets/src/mage/cards/q/QuestForRenewal.java b/Mage.Sets/src/mage/cards/q/QuestForRenewal.java index 2fbf44787b7..293e8c7d997 100644 --- a/Mage.Sets/src/mage/cards/q/QuestForRenewal.java +++ b/Mage.Sets/src/mage/cards/q/QuestForRenewal.java @@ -14,7 +14,6 @@ import mage.constants.CardType; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; /** * @@ -31,7 +30,7 @@ public final class QuestForRenewal extends CardImpl { // As long as there are four or more quest counters on Quest for Renewal, untap all creatures you control during each other player's untap step. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new UntapAllDuringEachOtherPlayersUntapStepEffect(new FilterControlledCreaturePermanent()), + new UntapAllDuringEachOtherPlayersUntapStepEffect(StaticFilters.FILTER_CONTROLLED_CREATURES), new SourceHasCounterCondition(CounterType.QUEST, 4, Integer.MAX_VALUE), "As long as there are four or more quest counters on {this}, untap all creatures you control during each other player's untap step."))); } diff --git a/Mage.Sets/src/mage/cards/q/QuestForTheGoblinLord.java b/Mage.Sets/src/mage/cards/q/QuestForTheGoblinLord.java index adde018418b..7d007ef1651 100644 --- a/Mage.Sets/src/mage/cards/q/QuestForTheGoblinLord.java +++ b/Mage.Sets/src/mage/cards/q/QuestForTheGoblinLord.java @@ -12,8 +12,8 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; /** * @@ -22,13 +22,7 @@ import mage.filter.common.FilterCreaturePermanent; public final class QuestForTheGoblinLord extends CardImpl { private static final String rule = "As long as {this} has five or more quest counters on it, creatures you control get +2/+0"; - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - private static final FilterPermanent goblinFilter = new FilterControlledCreaturePermanent("a Goblin"); - - static { - filter.add(TargetController.YOU.getControllerPredicate()); - goblinFilter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterPermanent goblinFilter = new FilterControlledPermanent(SubType.GOBLIN, "a Goblin"); public QuestForTheGoblinLord(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); @@ -37,7 +31,9 @@ public final class QuestForTheGoblinLord extends CardImpl { this.addAbility(new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.QUEST.createInstance()), goblinFilter, true)); // As long as Quest for the Goblin Lord has five or more quest counters on it, creatures you control get +2/+0. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new BoostAllEffect(2, 0, Duration.WhileOnBattlefield, filter, false), new SourceHasCounterCondition(CounterType.QUEST, 5, Integer.MAX_VALUE), rule))); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostAllEffect(2, 0, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURES, false), + new SourceHasCounterCondition(CounterType.QUEST, 5, Integer.MAX_VALUE), rule))); } private QuestForTheGoblinLord(final QuestForTheGoblinLord card) { diff --git a/Mage.Sets/src/mage/cards/q/Quicken.java b/Mage.Sets/src/mage/cards/q/Quicken.java index 830cc8c9cc4..240c18fbdc1 100644 --- a/Mage.Sets/src/mage/cards/q/Quicken.java +++ b/Mage.Sets/src/mage/cards/q/Quicken.java @@ -64,6 +64,7 @@ class QuickenAsThoughEffect extends AsThoughEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); quickenWatcher = game.getState().getWatcher(QuickenWatcher.class); Card card = game.getCard(source.getSourceId()); if (quickenWatcher != null && card != null) { diff --git a/Mage.Sets/src/mage/cards/r/Rackling.java b/Mage.Sets/src/mage/cards/r/Rackling.java index 3915d944812..f102b76ab91 100644 --- a/Mage.Sets/src/mage/cards/r/Rackling.java +++ b/Mage.Sets/src/mage/cards/r/Rackling.java @@ -57,7 +57,7 @@ class RacklingEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int damage = 3 - player.getHand().size(); if (damage > 0) { diff --git a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java index 4a4d143070c..f272f8e34d5 100644 --- a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java +++ b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java @@ -14,8 +14,8 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterCard; import mage.filter.StaticFilters; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.events.GameEvent; @@ -33,7 +33,7 @@ import java.util.UUID; */ public final class RadiantScrollwielder extends CardImpl { - private static final FilterCard filter = new FilterCard("instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("instant and sorcery spells you control"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/r/RagMan.java b/Mage.Sets/src/mage/cards/r/RagMan.java index 9d04369f140..7bfdf48b6a2 100644 --- a/Mage.Sets/src/mage/cards/r/RagMan.java +++ b/Mage.Sets/src/mage/cards/r/RagMan.java @@ -71,7 +71,7 @@ class RagManDiscardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Cards creatureCardsInHand = new CardsImpl(); for (UUID cardId : player.getHand()) { diff --git a/Mage.Sets/src/mage/cards/r/RaidingParty.java b/Mage.Sets/src/mage/cards/r/RaidingParty.java index 52fa45cd59f..177e814d2b0 100644 --- a/Mage.Sets/src/mage/cards/r/RaidingParty.java +++ b/Mage.Sets/src/mage/cards/r/RaidingParty.java @@ -18,10 +18,10 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterObject; import mage.filter.FilterStackObject; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -37,12 +37,11 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class RaidingParty extends CardImpl { - private static final FilterObject filterWhite = new FilterStackObject("white spells or abilities from white sources"); - private static final FilterControlledCreaturePermanent filterOrc = new FilterControlledCreaturePermanent("an Orc"); + private static final FilterStackObject filterWhite = new FilterStackObject("white spells or abilities from white sources"); + private static final FilterControlledPermanent filterOrc = new FilterControlledPermanent(SubType.ORC, "an Orc"); static { filterWhite.add(new ColorPredicate(ObjectColor.WHITE)); - filterOrc.add(SubType.ORC.getPredicate()); } public RaidingParty(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/r/RaisedByWolves.java b/Mage.Sets/src/mage/cards/r/RaisedByWolves.java index 18f02082dac..ea427308603 100644 --- a/Mage.Sets/src/mage/cards/r/RaisedByWolves.java +++ b/Mage.Sets/src/mage/cards/r/RaisedByWolves.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -14,7 +13,7 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.WolfToken; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -25,11 +24,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class RaisedByWolves extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Wolf you control"); - - static { - filter.add(SubType.WOLF.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.WOLF, "Wolf you control"); public RaisedByWolves(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/r/RakdosAugermage.java b/Mage.Sets/src/mage/cards/r/RakdosAugermage.java index d9c5091300e..fa868bf0ab0 100644 --- a/Mage.Sets/src/mage/cards/r/RakdosAugermage.java +++ b/Mage.Sets/src/mage/cards/r/RakdosAugermage.java @@ -66,7 +66,7 @@ class RakdosAugermageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (player != null && controller != null) { Cards revealedCards = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/r/RakdosPatronOfChaos.java b/Mage.Sets/src/mage/cards/r/RakdosPatronOfChaos.java new file mode 100644 index 00000000000..8f25f7f78c1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RakdosPatronOfChaos.java @@ -0,0 +1,69 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoUnlessTargetPlayerOrTargetsControllerPaysEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.common.TargetOpponent; + +/** + * + * @author DominionSpy + */ +public final class RakdosPatronOfChaos extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("nonland, nontoken permanents"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public RakdosPatronOfChaos(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // At the beginning of your end step, target opponent may sacrifice two nonland, nontoken permanents. If they don't, you draw two cards. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DoUnlessTargetPlayerOrTargetsControllerPaysEffect( + new DrawCardSourceControllerEffect(2), + new SacrificeTargetCost(2, filter)) + .setText("target opponent may sacrifice two nonland, nontoken permanents. " + + "If they don't, you draw two cards."), + TargetController.YOU, false); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private RakdosPatronOfChaos(final RakdosPatronOfChaos card) { + super(card); + } + + @Override + public RakdosPatronOfChaos copy() { + return new RakdosPatronOfChaos(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RallyingRoar.java b/Mage.Sets/src/mage/cards/r/RallyingRoar.java index 05cb501aa47..6b6c0847a69 100644 --- a/Mage.Sets/src/mage/cards/r/RallyingRoar.java +++ b/Mage.Sets/src/mage/cards/r/RallyingRoar.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -8,7 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -21,7 +20,7 @@ public final class RallyingRoar extends CardImpl { // Creatures you control get +1/+1 until end of turn. Untap them. this.getSpellAbility().addEffect(new BoostControlledEffect(1, 1, Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new UntapAllControllerEffect(new FilterControlledCreaturePermanent(), "Untap them.")); + this.getSpellAbility().addEffect(new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, "Untap them.")); } private RallyingRoar(final RallyingRoar card) { diff --git a/Mage.Sets/src/mage/cards/r/RampageOfTheClans.java b/Mage.Sets/src/mage/cards/r/RampageOfTheClans.java index e778f768168..3c825401a93 100644 --- a/Mage.Sets/src/mage/cards/r/RampageOfTheClans.java +++ b/Mage.Sets/src/mage/cards/r/RampageOfTheClans.java @@ -68,6 +68,7 @@ class RampageOfTheClansEffect extends OneShotEffect { playersWithPermanents.put(controllerId, playersWithPermanents.getOrDefault(controllerId, 0) + 1); } } + game.getState().processAction(game); Token token = new CentaurToken(); for (Map.Entry amountDestroyedByPlayer : playersWithPermanents.entrySet()) { token.putOntoBattlefield(amountDestroyedByPlayer.getValue(), game, source, amountDestroyedByPlayer.getKey()); diff --git a/Mage.Sets/src/mage/cards/r/RampagingRaptor.java b/Mage.Sets/src/mage/cards/r/RampagingRaptor.java index 82a445a0ffc..bf4ee11f65b 100644 --- a/Mage.Sets/src/mage/cards/r/RampagingRaptor.java +++ b/Mage.Sets/src/mage/cards/r/RampagingRaptor.java @@ -47,7 +47,7 @@ public final class RampagingRaptor extends CardImpl { // {2}{R}: Rampaging Raptor gets +2/+0 until end of turn. this.addAbility(new SimpleActivatedAbility( - new BoostSourceEffect(2, 0, Duration.EndOfTurn), new ManaCostsImpl("{2}{R}") + new BoostSourceEffect(2, 0, Duration.EndOfTurn), new ManaCostsImpl<>("{2}{R}") )); // Whenever Rampaging Raptor deals combat damage to an opponent, it deals that much damage to target planeswalker that player controls or battle that player protects. diff --git a/Mage.Sets/src/mage/cards/r/RatColony.java b/Mage.Sets/src/mage/cards/r/RatColony.java index 54246520805..619c6a21391 100644 --- a/Mage.Sets/src/mage/cards/r/RatColony.java +++ b/Mage.Sets/src/mage/cards/r/RatColony.java @@ -14,7 +14,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -23,7 +23,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; */ public final class RatColony extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("other Rat you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("other Rat you control"); static { filter.add(SubType.RAT.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/r/RatadrabikOfUrborg.java b/Mage.Sets/src/mage/cards/r/RatadrabikOfUrborg.java index 09e2ca1db6c..f86da51f7d6 100644 --- a/Mage.Sets/src/mage/cards/r/RatadrabikOfUrborg.java +++ b/Mage.Sets/src/mage/cards/r/RatadrabikOfUrborg.java @@ -94,7 +94,7 @@ class RatadrabikOfUrborgEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent copyFrom = targetPointer.getFirstTargetPermanentOrLKI(game, source); + Permanent copyFrom = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if(controller == null || copyFrom == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/r/RatchetBomb.java b/Mage.Sets/src/mage/cards/r/RatchetBomb.java index e698fb916f0..85c8b1a7feb 100644 --- a/Mage.Sets/src/mage/cards/r/RatchetBomb.java +++ b/Mage.Sets/src/mage/cards/r/RatchetBomb.java @@ -1,5 +1,3 @@ - - package mage.cards.r; import java.util.UUID; @@ -15,6 +13,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -45,42 +44,40 @@ public final class RatchetBomb extends CardImpl { return new RatchetBomb(this); } - class RatchetBombEffect extends OneShotEffect { +} - public RatchetBombEffect() { - super(Outcome.DestroyPermanent); - staticText = "Destroy each nonland permanent with mana value equal to the number of charge counters on {this}"; - } +class RatchetBombEffect extends OneShotEffect { - private RatchetBombEffect(final RatchetBombEffect effect) { - super(effect); - } + RatchetBombEffect() { + super(Outcome.DestroyPermanent); + staticText = "Destroy each nonland permanent with mana value equal to the number of charge counters on {this}"; + } - @Override - public boolean apply(Game game, Ability source) { - Permanent p = game.getBattlefield().getPermanent(source.getSourceId()); + private RatchetBombEffect(final RatchetBombEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent p = game.getBattlefield().getPermanent(source.getSourceId()); + if (p == null) { + p = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); if (p == null) { - p = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); - if (p == null) { - return false; - } + return false; } - - int count = p.getCounters(game).getCount(CounterType.CHARGE); - for (Permanent perm: game.getBattlefield().getAllActivePermanents()) { - if (perm.getManaValue() == count && !(perm.isLand(game))) { - perm.destroy(source, game, false); - } + } + int count = p.getCounters(game).getCount(CounterType.CHARGE); + for (Permanent perm : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_NON_LAND, source.getControllerId(), source, game)) { + if (perm.getManaValue() == count) { + perm.destroy(source, game, false); } - - return true; - } - - @Override - public RatchetBombEffect copy() { - return new RatchetBombEffect(this); } + return true; + } + @Override + public RatchetBombEffect copy() { + return new RatchetBombEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/r/RavenousBaloth.java b/Mage.Sets/src/mage/cards/r/RavenousBaloth.java index e970faafd9b..71bfccd8872 100644 --- a/Mage.Sets/src/mage/cards/r/RavenousBaloth.java +++ b/Mage.Sets/src/mage/cards/r/RavenousBaloth.java @@ -11,8 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -20,10 +19,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class RavenousBaloth extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Beast"); - static { - filter.add(SubType.BEAST.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.BEAST, "a Beast"); public RavenousBaloth(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/r/Realmwright.java b/Mage.Sets/src/mage/cards/r/Realmwright.java index becf404e95d..3e1d18ad70e 100644 --- a/Mage.Sets/src/mage/cards/r/Realmwright.java +++ b/Mage.Sets/src/mage/cards/r/Realmwright.java @@ -66,6 +66,11 @@ class RealmwrightEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + discard(); + return; + } + switch (choice) { case PLAINS: dependencyTypes.add(DependencyType.BecomePlains); diff --git a/Mage.Sets/src/mage/cards/r/RecklessCohort.java b/Mage.Sets/src/mage/cards/r/RecklessCohort.java index b2bddd03183..fbec66db691 100644 --- a/Mage.Sets/src/mage/cards/r/RecklessCohort.java +++ b/Mage.Sets/src/mage/cards/r/RecklessCohort.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -11,7 +10,7 @@ import mage.abilities.effects.common.combat.AttacksIfAbleSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -20,7 +19,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; */ public final class RecklessCohort extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Ally"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another Ally"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/r/Recommission.java b/Mage.Sets/src/mage/cards/r/Recommission.java index afa0b10cd2c..757b1f25a18 100644 --- a/Mage.Sets/src/mage/cards/r/Recommission.java +++ b/Mage.Sets/src/mage/cards/r/Recommission.java @@ -71,7 +71,7 @@ class RecommissionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (controller == null || card == null) { return false; } @@ -103,7 +103,7 @@ class RecommissionCounterEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return targetPointer.getTargets(game, source).contains(event.getTargetId()); + return getTargetPointer().getTargets(game, source).contains(event.getTargetId()); } @Override diff --git a/Mage.Sets/src/mage/cards/r/Recoup.java b/Mage.Sets/src/mage/cards/r/Recoup.java index dcaf2adcd05..049265e3588 100644 --- a/Mage.Sets/src/mage/cards/r/Recoup.java +++ b/Mage.Sets/src/mage/cards/r/Recoup.java @@ -69,7 +69,7 @@ class RecoupEffect extends ContinuousEffectImpl { if (player == null) { return false; } - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); diff --git a/Mage.Sets/src/mage/cards/r/RedDeathShipwrecker.java b/Mage.Sets/src/mage/cards/r/RedDeathShipwrecker.java new file mode 100644 index 00000000000..9109417e9d4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedDeathShipwrecker.java @@ -0,0 +1,88 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponentsCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class RedDeathShipwrecker extends CardImpl { + + public RedDeathShipwrecker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CRAB); + this.subtype.add(SubType.MUTANT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Alluring Eyes -- {T}: Goad target creature an opponent controls. That player draws a card. You add {R}. + Ability ability = new SimpleActivatedAbility( + new GoadTargetEffect().setText("goad target creature an opponent controls"), new TapSourceCost()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + ability.addEffect(new AlluringEyesDrawEffect()); + ability.addEffect(new BasicManaEffect(Mana.RedMana(1)) + .setText("you add {R}. (Until your next turn, that creature attacks each combat if able " + + "and attacks a player other than you if able.)")); + this.addAbility(ability.withFlavorWord("Alluring Eyes")); + } + + private RedDeathShipwrecker(final RedDeathShipwrecker card) { + super(card); + } + + @Override + public RedDeathShipwrecker copy() { + return new RedDeathShipwrecker(this); + } +} + +class AlluringEyesDrawEffect extends OneShotEffect { + + AlluringEyesDrawEffect() { + super(Outcome.Benefit); + staticText = "that player draws a card"; + } + + private AlluringEyesDrawEffect(final AlluringEyesDrawEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + player.drawCards(1, source, game); + } + return true; + } + + @Override + public AlluringEyesDrawEffect copy() { + return new AlluringEyesDrawEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RedSunsTwilight.java b/Mage.Sets/src/mage/cards/r/RedSunsTwilight.java index 4db5a3bd170..cc360864b77 100644 --- a/Mage.Sets/src/mage/cards/r/RedSunsTwilight.java +++ b/Mage.Sets/src/mage/cards/r/RedSunsTwilight.java @@ -86,7 +86,7 @@ class RedSunsTwilightEffect extends OneShotEffect { } // Try to destroy the artifacts List destroyedArtifacts = new ArrayList<>(); - for (UUID targetID : this.targetPointer.getTargets(game, source)) { + for (UUID targetID : this.getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetID); if (permanent != null) { if (permanent.destroy(source, game, false)) { diff --git a/Mage.Sets/src/mage/cards/r/RedemptionArc.java b/Mage.Sets/src/mage/cards/r/RedemptionArc.java new file mode 100644 index 00000000000..845c59ddf80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedemptionArc.java @@ -0,0 +1,62 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileAttachedEffect; +import mage.abilities.effects.common.combat.GoadAttachedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.*; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.effects.common.AttachEffect; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; + +/** + * + * @author jimga150 + */ +public final class RedemptionArc extends CardImpl { + + public RedemptionArc(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature has indestructible and is goaded. + Effect effect = new GainAbilityAttachedEffect(IndestructibleAbility.getInstance(), + AttachmentType.AURA, Duration.WhileOnBattlefield); + + Effect effect2 = new GoadAttachedEffect(); + + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); + ability.addEffect(effect2); + ability.addCustomOutcome(Outcome.Protect); + this.addAbility(ability); + + // {1}{W}: Exile enchanted creature. + this.addAbility(new SimpleActivatedAbility(new ExileAttachedEffect(), new ManaCostsImpl<>("{1}{W}"))); + } + + private RedemptionArc(final RedemptionArc card) { + super(card); + } + + @Override + public RedemptionArc copy() { + return new RedemptionArc(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReenactTheCrime.java b/Mage.Sets/src/mage/cards/r/ReenactTheCrime.java new file mode 100644 index 00000000000..92a08e3bbf5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReenactTheCrime.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.effects.common.ExileTargetCardCopyAndCastEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.card.PutIntoGraveFromAnywhereThisTurnPredicate; +import mage.target.common.TargetCardInGraveyard; +import mage.watchers.common.CardsPutIntoGraveyardWatcher; + +/** + * + * @author DominionSpy + */ +public final class ReenactTheCrime extends CardImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard(); + + static { + filter.add(PutIntoGraveFromAnywhereThisTurnPredicate.instance); + } + + public ReenactTheCrime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{U}{U}"); + + // Exile target nonland card in a graveyard that was put there from anywhere this turn. + // Copy it. You may cast the copy without paying its mana cost. + getSpellAbility().addEffect(new ExileTargetCardCopyAndCastEffect(true) + .setText("Exile target nonland card in a graveyard that was put there from anywhere this turn. " + + "Copy it. You may cast the copy without paying its mana cost.")); + getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + getSpellAbility().addWatcher(new CardsPutIntoGraveyardWatcher()); + + } + + private ReenactTheCrime(final ReenactTheCrime card) { + super(card); + } + + @Override + public ReenactTheCrime copy() { + return new ReenactTheCrime(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RefractionTrap.java b/Mage.Sets/src/mage/cards/r/RefractionTrap.java index 96e96113562..2c9c52c290b 100644 --- a/Mage.Sets/src/mage/cards/r/RefractionTrap.java +++ b/Mage.Sets/src/mage/cards/r/RefractionTrap.java @@ -106,8 +106,8 @@ class RefractionTrapPreventDamageEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/r/RelicSloth.java b/Mage.Sets/src/mage/cards/r/RelicSloth.java index 3c47b0e3630..5a092039b1c 100644 --- a/Mage.Sets/src/mage/cards/r/RelicSloth.java +++ b/Mage.Sets/src/mage/cards/r/RelicSloth.java @@ -18,7 +18,7 @@ public final class RelicSloth extends CardImpl { public RelicSloth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{W}"); - this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.SLOTH, SubType.BEAST); this.power = new MageInt(4); this.toughness = new MageInt(4); diff --git a/Mage.Sets/src/mage/cards/r/ReliveThePast.java b/Mage.Sets/src/mage/cards/r/ReliveThePast.java new file mode 100644 index 00000000000..d07eca14ebf --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReliveThePast.java @@ -0,0 +1,58 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterEnchantmentCard; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author DominionSpy + */ +public final class ReliveThePast extends CardImpl { + + private static final FilterEnchantmentCard filter = new FilterEnchantmentCard("non-Aura enchantment"); + + static { + filter.add(Predicates.not(SubType.AURA.getPredicate())); + } + + public ReliveThePast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{G}{W}"); + + // Return up to one target artifact card, up to one target land card, and up to one target non-Aura enchantment card from your graveyard to the battlefield. + getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect() + .setTargetPointer(new EachTargetPointer()) + .setText("Return up to one target artifact card, up to one target land card, and up to one target non-Aura enchantment card from your graveyard to the battlefield.")); + getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_ARTIFACT)); + getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_LAND)); + getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + + // They are 5/5 Elemental creatures in addition to their other types. + getSpellAbility().addEffect(new BecomesCreatureTargetEffect( + new CreatureToken(5, 5, "5/5 Elemental creatures", SubType.ELEMENTAL), + false, false, Duration.Custom) + .setTargetPointer(new EachTargetPointer()) + .setText("They are 5/5 Elemental creatures in addition to their other types.")); + } + + private ReliveThePast(final ReliveThePast card) { + super(card); + } + + @Override + public ReliveThePast copy() { + return new ReliveThePast(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RemoteFarm.java b/Mage.Sets/src/mage/cards/r/RemoteFarm.java index 0337bf248db..83584b66def 100644 --- a/Mage.Sets/src/mage/cards/r/RemoteFarm.java +++ b/Mage.Sets/src/mage/cards/r/RemoteFarm.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,8 +30,11 @@ public final class RemoteFarm extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); // Remote Farm enters the battlefield tapped with two depletion counters on it. - this.addAbility(new EntersBattlefieldTappedAbility()); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2)))); + Ability etbAbility = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with two depletion counters on it" + ); + etbAbility.addEffect(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2))); + this.addAbility(etbAbility); // {tap}, Remove a depletion counter from Remote Farm: Add {W}{W}. If there are no depletion counters on Remote Farm, sacrifice it. Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(2), new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.DEPLETION.createInstance(1))); diff --git a/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java b/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java index 4763a16e24f..36c497c79cc 100644 --- a/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java +++ b/Mage.Sets/src/mage/cards/r/RepeatedReverberation.java @@ -122,7 +122,7 @@ class RepeatedReverberationEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackAbility == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/r/RepulsiveMutation.java b/Mage.Sets/src/mage/cards/r/RepulsiveMutation.java new file mode 100644 index 00000000000..decf8f36f55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RepulsiveMutation.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +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.filter.StaticFilters; +import mage.target.TargetSpell; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +/** + * + * @author DominionSpy + */ +public final class RepulsiveMutation extends CardImpl { + + public RepulsiveMutation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{G}{U}"); + + // Put X +1/+1 counters on target creature you control. + getSpellAbility().addEffect(new AddCountersTargetEffect( + CounterType.P1P1.createInstance(), ManacostVariableValue.REGULAR)); + getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + + // Then counter up to one target spell unless its controller pays mana equal to the greatest power among creatures you control. + getSpellAbility().addEffect(new CounterUnlessPaysEffect(GreatestPowerAmongControlledCreaturesValue.instance) + .setTargetPointer(new SecondTargetPointer()) + .setText("Then counter up to one target spell unless its controller pays mana equal to the greatest power among creatures you control.")); + getSpellAbility().addTarget(new TargetSpell(0, 1, StaticFilters.FILTER_SPELL)); + } + + private RepulsiveMutation(final RepulsiveMutation card) { + super(card); + } + + @Override + public RepulsiveMutation copy() { + return new RepulsiveMutation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java b/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java index d246fe85b07..3421512625b 100644 --- a/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java +++ b/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java @@ -90,7 +90,7 @@ class ResplendentMarshalEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); - Card exiledCard = game.getCard(targetPointer.getFirst(game, source)); + Card exiledCard = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && sourceObject != null && exiledCard != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents( StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, source.getControllerId(), source, game)) { diff --git a/Mage.Sets/src/mage/cards/r/Rethink.java b/Mage.Sets/src/mage/cards/r/Rethink.java index 35192dd77f2..2b0f1efc0ae 100644 --- a/Mage.Sets/src/mage/cards/r/Rethink.java +++ b/Mage.Sets/src/mage/cards/r/Rethink.java @@ -56,7 +56,7 @@ class RethinkEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/r/RevengeStarWars.java b/Mage.Sets/src/mage/cards/r/RevengeStarWars.java index 7d8fc52c91d..b3a4863c196 100644 --- a/Mage.Sets/src/mage/cards/r/RevengeStarWars.java +++ b/Mage.Sets/src/mage/cards/r/RevengeStarWars.java @@ -85,7 +85,7 @@ class RevengeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null && target.isCreature(game)) { ContinuousEffect effect = new BoostTargetEffect(4, 0, Duration.EndOfTurn); effect.setTargetPointer(new FixedTarget(target.getId(), game)); diff --git a/Mage.Sets/src/mage/cards/r/ReverseDamage.java b/Mage.Sets/src/mage/cards/r/ReverseDamage.java index 5f8d332a596..24ad5920abe 100644 --- a/Mage.Sets/src/mage/cards/r/ReverseDamage.java +++ b/Mage.Sets/src/mage/cards/r/ReverseDamage.java @@ -61,6 +61,7 @@ class ReverseDamageEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/r/ReviveTheFallen.java b/Mage.Sets/src/mage/cards/r/ReviveTheFallen.java index 96524669fff..110d15edd86 100644 --- a/Mage.Sets/src/mage/cards/r/ReviveTheFallen.java +++ b/Mage.Sets/src/mage/cards/r/ReviveTheFallen.java @@ -9,6 +9,7 @@ import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -23,7 +24,7 @@ public final class ReviveTheFallen extends CardImpl { // Return target creature card from a graveyard to its owner's hand. this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // Clash with an opponent. If you win, return Revive the Fallen to its owner's hand. this.getSpellAbility().addEffect(new DoIfClashWonEffect(ReturnToHandSpellEffect.getInstance())); } diff --git a/Mage.Sets/src/mage/cards/r/Rey.java b/Mage.Sets/src/mage/cards/r/Rey.java index a36fd331957..ef4e99e6a04 100644 --- a/Mage.Sets/src/mage/cards/r/Rey.java +++ b/Mage.Sets/src/mage/cards/r/Rey.java @@ -75,7 +75,7 @@ class ReyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if(targetPlayer != null && controller != null) { if(targetPlayer.getLibrary().hasCards()) { // reveal the top card of target player's library. diff --git a/Mage.Sets/src/mage/cards/r/RhysticStudy.java b/Mage.Sets/src/mage/cards/r/RhysticStudy.java index 81e35e7f091..997953129e5 100644 --- a/Mage.Sets/src/mage/cards/r/RhysticStudy.java +++ b/Mage.Sets/src/mage/cards/r/RhysticStudy.java @@ -59,7 +59,7 @@ class RhysticStudyDrawEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (controller != null && opponent != null && sourceObject != null) { if (controller.chooseUse(Outcome.DrawCard, "Draw a card (" + sourceObject.getLogName() + ')', source, game)) { diff --git a/Mage.Sets/src/mage/cards/r/RideTheAvalanche.java b/Mage.Sets/src/mage/cards/r/RideTheAvalanche.java index b27922c05b8..c7d4b60948c 100644 --- a/Mage.Sets/src/mage/cards/r/RideTheAvalanche.java +++ b/Mage.Sets/src/mage/cards/r/RideTheAvalanche.java @@ -57,6 +57,7 @@ class RideTheAvalancheAsThoughEffect extends AsThoughEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); RideTheAvalancheWatcher.addPlayer(source.getControllerId(), game); } diff --git a/Mage.Sets/src/mage/cards/r/Riftsweeper.java b/Mage.Sets/src/mage/cards/r/Riftsweeper.java index 61ad612b66a..b832fe05f39 100644 --- a/Mage.Sets/src/mage/cards/r/Riftsweeper.java +++ b/Mage.Sets/src/mage/cards/r/Riftsweeper.java @@ -72,7 +72,7 @@ class RiftsweeperEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { // remove existing suspend counters card.getCounters(game).clear(); diff --git a/Mage.Sets/src/mage/cards/r/RighteousAuthority.java b/Mage.Sets/src/mage/cards/r/RighteousAuthority.java index f18d616596e..0e5e7799fd1 100644 --- a/Mage.Sets/src/mage/cards/r/RighteousAuthority.java +++ b/Mage.Sets/src/mage/cards/r/RighteousAuthority.java @@ -44,7 +44,8 @@ public final class RighteousAuthority extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(boost, boost, Duration.WhileOnBattlefield))); // At the beginning of the draw step of enchanted creature's controller, that player draws an additional card. - this.addAbility(new BeginningOfDrawTriggeredAbility(new DrawCardTargetEffect(1), TargetController.CONTROLLER_ATTACHED_TO, false)); + this.addAbility(new BeginningOfDrawTriggeredAbility(new DrawCardTargetEffect(1) + .setText("that player draws an additional card"), TargetController.CONTROLLER_ATTACHED_TO, false)); } private RighteousAuthority(final RighteousAuthority card) { diff --git a/Mage.Sets/src/mage/cards/r/RinAndSeriInseparable.java b/Mage.Sets/src/mage/cards/r/RinAndSeriInseparable.java index e74384272f8..140d344230d 100644 --- a/Mage.Sets/src/mage/cards/r/RinAndSeriInseparable.java +++ b/Mage.Sets/src/mage/cards/r/RinAndSeriInseparable.java @@ -20,7 +20,7 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.GreenCatToken; import mage.game.permanent.token.WhiteDogToken; import mage.target.common.TargetAnyTarget; @@ -35,8 +35,8 @@ public final class RinAndSeriInseparable extends CardImpl { private static final FilterSpell dogSpellFilter = new FilterSpell("a Dog spell"); private static final FilterSpell catSpellFilter = new FilterSpell("a Cat spell"); - private static final FilterPermanent dogPermanentFilter = new FilterControlledCreaturePermanent("Dogs you control"); - private static final FilterPermanent catPermanentFilter = new FilterControlledCreaturePermanent("Cats you control"); + private static final FilterPermanent dogPermanentFilter = new FilterControlledPermanent("Dogs you control"); + private static final FilterPermanent catPermanentFilter = new FilterControlledPermanent("Cats you control"); static { dogSpellFilter.add(SubType.DOG.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/r/RingOfMaruf.java b/Mage.Sets/src/mage/cards/r/RingOfMaruf.java index db21908ebcf..25fddf0d9f2 100644 --- a/Mage.Sets/src/mage/cards/r/RingOfMaruf.java +++ b/Mage.Sets/src/mage/cards/r/RingOfMaruf.java @@ -51,7 +51,7 @@ class RingOfMarufEffect extends ReplacementEffectImpl { RingOfMarufEffect() { super(Duration.EndOfTurn, Outcome.Benefit); - staticText = "The next time you would draw a card this turn, instead choose a card you own from outside the game and put it into your hand."; + staticText = "The next time you would draw a card this turn, instead put a card you own from outside the game into your hand"; } private RingOfMarufEffect(final RingOfMarufEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RiptideDirector.java b/Mage.Sets/src/mage/cards/r/RiptideDirector.java index 78d83596bb8..d382e00e5c0 100644 --- a/Mage.Sets/src/mage/cards/r/RiptideDirector.java +++ b/Mage.Sets/src/mage/cards/r/RiptideDirector.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; /** @@ -23,11 +22,7 @@ import mage.filter.common.FilterControlledPermanent; */ public final class RiptideDirector extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("Wizard you control"); - - static { - filter.add(SubType.WIZARD.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.WIZARD, "Wizard you control"); public RiptideDirector(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}{U}"); diff --git a/Mage.Sets/src/mage/cards/r/RiseFall.java b/Mage.Sets/src/mage/cards/r/RiseFall.java index 0adf51a8418..9187668d0e7 100644 --- a/Mage.Sets/src/mage/cards/r/RiseFall.java +++ b/Mage.Sets/src/mage/cards/r/RiseFall.java @@ -25,15 +25,13 @@ import java.util.UUID; */ public final class RiseFall extends SplitCard { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public RiseFall(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{B}", "{B}{R}", SpellAbilityType.SPLIT); // Rise // Return target creature card from a graveyard and target creature on the battlefield to their owners' hands. getLeftHalfCard().getSpellAbility().addEffect(new RiseEffect()); - getLeftHalfCard().getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + getLeftHalfCard().getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); // Fall diff --git a/Mage.Sets/src/mage/cards/r/RiseFromTheGrave.java b/Mage.Sets/src/mage/cards/r/RiseFromTheGrave.java index 709aab8bba7..542db946b83 100644 --- a/Mage.Sets/src/mage/cards/r/RiseFromTheGrave.java +++ b/Mage.Sets/src/mage/cards/r/RiseFromTheGrave.java @@ -8,6 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -22,7 +23,7 @@ public final class RiseFromTheGrave extends CardImpl { // Put target creature card from a graveyard onto the battlefield under your control. That creature is a black Zombie in addition to its other colors and types. - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); this.getSpellAbility().addEffect(new AddCreatureTypeAdditionEffect(SubType.ZOMBIE, true)); } diff --git a/Mage.Sets/src/mage/cards/r/RishadanPawnshop.java b/Mage.Sets/src/mage/cards/r/RishadanPawnshop.java index cacfabadcd9..a86adfc6b7f 100644 --- a/Mage.Sets/src/mage/cards/r/RishadanPawnshop.java +++ b/Mage.Sets/src/mage/cards/r/RishadanPawnshop.java @@ -73,7 +73,7 @@ class RishadanPawnshopShuffleIntoLibraryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { Player owner = game.getPlayer(permanent.getOwnerId()); if (owner != null) { diff --git a/Mage.Sets/src/mage/cards/r/RiskyMove.java b/Mage.Sets/src/mage/cards/r/RiskyMove.java index dbefd9063fa..641e989da14 100644 --- a/Mage.Sets/src/mage/cards/r/RiskyMove.java +++ b/Mage.Sets/src/mage/cards/r/RiskyMove.java @@ -1,9 +1,7 @@ package mage.cards.r; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.ContinuousEffect; @@ -13,7 +11,6 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -23,8 +20,9 @@ import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author L_J */ public final class RiskyMove extends CardImpl { @@ -100,7 +98,7 @@ class RiskyMoveGetControlEffect extends OneShotEffect { class RiskyMoveTriggeredAbility extends TriggeredAbilityImpl { - public RiskyMoveTriggeredAbility() { + RiskyMoveTriggeredAbility() { super(Zone.BATTLEFIELD, new RiskyMoveFlipCoinEffect(), false); setTriggerPhrase("When you gain control of {this} from another player, "); } @@ -145,7 +143,7 @@ class RiskyMoveFlipCoinEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Target target1 = new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true); + Target target1 = new TargetControlledCreaturePermanent().withNotTarget(true); Target target2 = new TargetOpponent(true); if (target1.canChoose(controller.getId(), source, game)) { @@ -180,9 +178,9 @@ class RiskyMoveFlipCoinEffect extends OneShotEffect { class RiskyMoveCreatureGainControlEffect extends ContinuousEffectImpl { - private UUID controller; + private final UUID controller; - public RiskyMoveCreatureGainControlEffect(Duration duration, UUID controller) { + RiskyMoveCreatureGainControlEffect(Duration duration, UUID controller) { super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); this.controller = controller; this.staticText = "If you lose the flip, that opponent gains control of that creature"; @@ -200,10 +198,11 @@ class RiskyMoveCreatureGainControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (targetPointer != null) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + permanent = game.getPermanent(source.getFirstTarget()); } + if (permanent != null) { return permanent.changeControllerId(controller, game, source); } diff --git a/Mage.Sets/src/mage/cards/r/RiteOfTheRagingStorm.java b/Mage.Sets/src/mage/cards/r/RiteOfTheRagingStorm.java index 028b2b40eb7..9f9640400a4 100644 --- a/Mage.Sets/src/mage/cards/r/RiteOfTheRagingStorm.java +++ b/Mage.Sets/src/mage/cards/r/RiteOfTheRagingStorm.java @@ -63,7 +63,7 @@ class RiteOfTheRagingStormEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { return new LightningRagerToken().putOntoBattlefield(1, game, source, player.getId()); } diff --git a/Mage.Sets/src/mage/cards/r/RoarOfTheKha.java b/Mage.Sets/src/mage/cards/r/RoarOfTheKha.java index f512408c691..9283ea78f91 100644 --- a/Mage.Sets/src/mage/cards/r/RoarOfTheKha.java +++ b/Mage.Sets/src/mage/cards/r/RoarOfTheKha.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -27,7 +26,7 @@ public final class RoarOfTheKha extends CardImpl { this.getSpellAbility().addEffect(new BoostControlledEffect(1, 1, Duration.EndOfTurn)); // or untap all creatures you control. - Mode mode = new Mode(new UntapAllControllerEffect(new FilterControlledCreaturePermanent(), rule)); + Mode mode = new Mode(new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, rule)); this.getSpellAbility().getModes().addMode(mode); // Entwine {1}{W} diff --git a/Mage.Sets/src/mage/cards/r/RoaringPrimadox.java b/Mage.Sets/src/mage/cards/r/RoaringPrimadox.java index 792c177ab2b..48cadc0c2e3 100644 --- a/Mage.Sets/src/mage/cards/r/RoaringPrimadox.java +++ b/Mage.Sets/src/mage/cards/r/RoaringPrimadox.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -9,7 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.events.GameEvent.EventType; /** @@ -26,7 +25,7 @@ public final class RoaringPrimadox extends CardImpl { this.toughness = new MageInt(4); // At the beginning of your upkeep, return a creature you control to its owner's hand. - this.addAbility(new OnEventTriggeredAbility(EventType.UPKEEP_STEP_PRE, "beginning of your upkeep", new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()))); + this.addAbility(new OnEventTriggeredAbility(EventType.UPKEEP_STEP_PRE, "beginning of your upkeep", new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE))); } private RoaringPrimadox(final RoaringPrimadox card) { diff --git a/Mage.Sets/src/mage/cards/r/RobberFly.java b/Mage.Sets/src/mage/cards/r/RobberFly.java index 9bea2fc2b8e..e711aa350bd 100644 --- a/Mage.Sets/src/mage/cards/r/RobberFly.java +++ b/Mage.Sets/src/mage/cards/r/RobberFly.java @@ -65,7 +65,7 @@ class DrawCardsDefendingPlayerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player defendingPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player defendingPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && defendingPlayer != null) { int numberOfCardsInHand = defendingPlayer.getHand().size(); diff --git a/Mage.Sets/src/mage/cards/r/RonaDiscipleOfGix.java b/Mage.Sets/src/mage/cards/r/RonaDiscipleOfGix.java index a206032303c..662ef87239a 100644 --- a/Mage.Sets/src/mage/cards/r/RonaDiscipleOfGix.java +++ b/Mage.Sets/src/mage/cards/r/RonaDiscipleOfGix.java @@ -11,7 +11,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -26,7 +26,6 @@ import mage.constants.Zone; import mage.filter.common.FilterHistoricCard; import mage.game.ExileZone; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; import mage.util.CardUtil; @@ -59,7 +58,10 @@ public final class RonaDiscipleOfGix extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new RonaDiscipleOfGixPlayNonLandEffect())); // {4}, {T}: Exile the top card of your library. - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RonaDiscipleOfGixExileEffect(), new GenericManaCost(4)); + ability = new SimpleActivatedAbility( + Zone.BATTLEFIELD, + new ExileCardsFromTopOfLibraryControllerEffect(1, true), + new GenericManaCost(4)); ability.addCost(new TapSourceCost()); this.addAbility(ability); } @@ -111,35 +113,3 @@ class RonaDiscipleOfGixPlayNonLandEffect extends AsThoughEffectImpl { return false; } } - -class RonaDiscipleOfGixExileEffect extends OneShotEffect { - - RonaDiscipleOfGixExileEffect() { - super(Outcome.Exile); - this.staticText = "Exile the top card of your library"; - } - - private RonaDiscipleOfGixExileEffect(final RonaDiscipleOfGixExileEffect effect) { - super(effect); - } - - @Override - public RonaDiscipleOfGixExileEffect copy() { - return new RonaDiscipleOfGixExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - controller.moveCardsToExile(card, source, game, true, exileId, sourceObject.getIdName()); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RoninCliffrider.java b/Mage.Sets/src/mage/cards/r/RoninCliffrider.java index de4898d4917..ab6f1bd3a45 100644 --- a/Mage.Sets/src/mage/cards/r/RoninCliffrider.java +++ b/Mage.Sets/src/mage/cards/r/RoninCliffrider.java @@ -1,22 +1,16 @@ - package mage.cards.r; -import java.util.List; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllControlledTargetEffect; import mage.abilities.keyword.BushidoAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; + +import java.util.UUID; /** * @@ -35,7 +29,9 @@ public final class RoninCliffrider extends CardImpl { // Bushido 1 this.addAbility(new BushidoAbility(1)); // Whenever Ronin Cliffrider attacks, you may have it deal 1 damage to each creature defending player controls. - this.addAbility(new AttacksTriggeredAbility(new RoninCliffriderEffect(), true)); + this.addAbility(new AttacksTriggeredAbility(new DamageAllControlledTargetEffect(1) + .setText("you may have it deal 1 damage to each creature defending player controls"), + true, null, SetTargetPointer.PLAYER)); } private RoninCliffrider(final RoninCliffrider card) { @@ -47,34 +43,3 @@ public final class RoninCliffrider extends CardImpl { return new RoninCliffrider(this); } } -class RoninCliffriderEffect extends OneShotEffect { - - RoninCliffriderEffect() { - super(Outcome.Damage); - this.staticText = "you may have it deal 1 damage to each creature defending player controls"; - } - - private RoninCliffriderEffect(final RoninCliffriderEffect effect) { - super(effect); - } - - @Override - public RoninCliffriderEffect copy() { - return new RoninCliffriderEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - UUID defenderId = game.getCombat().getDefenderId(source.getSourceId()); - if (defenderId != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent(); - filter.add(new ControllerIdPredicate(defenderId)); - List permanents = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game); - for (Permanent permanent : permanents) { - permanent.damage(1, source.getSourceId(), source, game, false, true); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RoseNoble.java b/Mage.Sets/src/mage/cards/r/RoseNoble.java new file mode 100644 index 00000000000..f431d8c06e0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoseNoble.java @@ -0,0 +1,66 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RoseNoble extends CardImpl { + + private static final FilterSpell filter + = new FilterSpell("a Doctor spell or creature spell with doctor's companion"); + + static { + filter.add(Predicates.or( + SubType.DOCTOR.getPredicate(), + Predicates.and( + CardType.CREATURE.getPredicate(), + new AbilityPredicate(DoctorsCompanionAbility.class) + ) + )); + } + + public RoseNoble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // Whenever you cast a Doctor spell or creature spell with doctor's companion, draw a card. + this.addAbility(new SpellCastControllerTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter, false + )); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private RoseNoble(final RoseNoble card) { + super(card); + } + + @Override + public RoseNoble copy() { + return new RoseNoble(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RotfeasterMaggot.java b/Mage.Sets/src/mage/cards/r/RotfeasterMaggot.java index 1c3067d2afa..76cb7033ac4 100644 --- a/Mage.Sets/src/mage/cards/r/RotfeasterMaggot.java +++ b/Mage.Sets/src/mage/cards/r/RotfeasterMaggot.java @@ -13,7 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInGraveyard; @@ -33,7 +33,7 @@ public final class RotfeasterMaggot extends CardImpl { // When Rotfeaster Maggot enters the battlefield, exile target creature card from a graveyard. You gain life equal to that card's toughness. Ability ability = new EntersBattlefieldTriggeredAbility(new RotfeasterMaggotExileEffect(), false); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RowanFearlessSparkmage.java b/Mage.Sets/src/mage/cards/r/RowanFearlessSparkmage.java index 1048708ee47..53bc75ab66d 100644 --- a/Mage.Sets/src/mage/cards/r/RowanFearlessSparkmage.java +++ b/Mage.Sets/src/mage/cards/r/RowanFearlessSparkmage.java @@ -3,14 +3,11 @@ package mage.cards.r; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.UntapAllEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.common.continuous.GainControlAllEffect; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; import mage.abilities.keyword.FirstStrikeAbility; -import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -53,14 +50,7 @@ public final class RowanFearlessSparkmage extends CardImpl { this.addAbility(ability); // −9: Gain control of all creatures until end of turn. Untap them. They gain haste until end of turn. - ability = new LoyaltyAbility(new GainControlAllEffect(Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES), -9); - ability.addEffect(new UntapAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES).setText("untap them")); - ability.addEffect(new GainAbilityAllEffect( - HasteAbility.getInstance(), - Duration.EndOfTurn, - StaticFilters.FILTER_PERMANENT_CREATURES, - "they gain haste until end of turn" - )); + ability = new LoyaltyAbility(new GainControlAllUntapGainHasteEffect(StaticFilters.FILTER_PERMANENT_CREATURES), -9); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RowanScholarOfSparks.java b/Mage.Sets/src/mage/cards/r/RowanScholarOfSparks.java index 44048a03681..34a9aeb7a60 100644 --- a/Mage.Sets/src/mage/cards/r/RowanScholarOfSparks.java +++ b/Mage.Sets/src/mage/cards/r/RowanScholarOfSparks.java @@ -158,7 +158,7 @@ class WillScholarOfFrostEffect extends OneShotEffect { if (player == null) { return false; } - Cards cards = new CardsImpl(targetPointer.getTargets(game, source)); + Cards cards = new CardsImpl(getTargetPointer().getTargets(game, source)); Map playerMap = cards .getCards(game) .stream() diff --git a/Mage.Sets/src/mage/cards/r/RuinsRecluse.java b/Mage.Sets/src/mage/cards/r/RuinsRecluse.java index e4d40dc9814..f4a51cab9eb 100644 --- a/Mage.Sets/src/mage/cards/r/RuinsRecluse.java +++ b/Mage.Sets/src/mage/cards/r/RuinsRecluse.java @@ -34,7 +34,7 @@ public final class RuinsRecluse extends CardImpl { // {3}{G}: Put a +1/+1 counter on Ruins Recluse. this.addAbility(new SimpleActivatedAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl("{3}{G}") + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{3}{G}") )); } diff --git a/Mage.Sets/src/mage/cards/r/RumblingAftershocks.java b/Mage.Sets/src/mage/cards/r/RumblingAftershocks.java index 9841ba88ec7..43006f460c6 100644 --- a/Mage.Sets/src/mage/cards/r/RumblingAftershocks.java +++ b/Mage.Sets/src/mage/cards/r/RumblingAftershocks.java @@ -96,12 +96,12 @@ class RumblingAftershocksDealDamageEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); Integer damageAmount = (Integer) this.getValue("damageAmount"); if (player != null && damageAmount > 0) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(damageAmount, source.getSourceId(), source, game); return true; } - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(damageAmount, source.getSourceId(), source, game, false, true); return true; diff --git a/Mage.Sets/src/mage/cards/r/RuneflareTrap.java b/Mage.Sets/src/mage/cards/r/RuneflareTrap.java index 58924058dff..99fc5c5dbdf 100644 --- a/Mage.Sets/src/mage/cards/r/RuneflareTrap.java +++ b/Mage.Sets/src/mage/cards/r/RuneflareTrap.java @@ -1,4 +1,3 @@ - package mage.cards.r; import mage.abilities.Ability; @@ -33,7 +32,8 @@ public final class RuneflareTrap extends CardImpl { this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl<>("{R}"), RuneflareTrapCondition.instance), new CardsAmountDrawnThisTurnWatcher()); // Runeflare Trap deals damage to target player equal to the number of cards in that player's hand. - this.getSpellAbility().addEffect(new DamageTargetEffect(new TargetPlayerCardsInHandCount())); + this.getSpellAbility().addEffect(new DamageTargetEffect(new TargetPlayerCardsInHandCount()) + .setText("{this} deals damage to target player equal to the number of cards in that player's hand")); this.getSpellAbility().addTarget(new TargetPlayer()); } diff --git a/Mage.Sets/src/mage/cards/r/Runesword.java b/Mage.Sets/src/mage/cards/r/Runesword.java index 4206caf3ad9..c171002797f 100644 --- a/Mage.Sets/src/mage/cards/r/Runesword.java +++ b/Mage.Sets/src/mage/cards/r/Runesword.java @@ -120,6 +120,7 @@ class RuneswordCantBeRegeneratedEffect extends ContinuousRuleModifyingEffectImpl } public void init(Ability source, Game game) { + super.init(source, game); targetCreatureId = getTargetPointer().getFirst(game, source); } diff --git a/Mage.Sets/src/mage/cards/r/RushOfBattle.java b/Mage.Sets/src/mage/cards/r/RushOfBattle.java index 0210ecb7d7a..149a5850b7f 100644 --- a/Mage.Sets/src/mage/cards/r/RushOfBattle.java +++ b/Mage.Sets/src/mage/cards/r/RushOfBattle.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -19,19 +18,14 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class RushOfBattle extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - static { - filter.add(SubType.WARRIOR.getPredicate()); - } + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.WARRIOR, "Warrior creatures you control"); public RushOfBattle(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{W}"); - // Creatures you control get +2/+1 until end of turn. Warrior creatures you control gain lifelink until end of turn. this.getSpellAbility().addEffect(new BoostControlledEffect(2, 1, Duration.EndOfTurn)); Effect effect = new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, filter); - effect.setText("Warrior creatures you control gain lifelink until end of turn"); this.getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/s/SaltRoadAmbushers.java b/Mage.Sets/src/mage/cards/s/SaltRoadAmbushers.java index 44332a5de6d..07b33a5dadd 100644 --- a/Mage.Sets/src/mage/cards/s/SaltRoadAmbushers.java +++ b/Mage.Sets/src/mage/cards/s/SaltRoadAmbushers.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -12,7 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -48,13 +47,13 @@ public final class SaltRoadAmbushers extends CardImpl { class SaltRoadAmbushersTriggeredAbility extends TurnedFaceUpAllTriggeredAbility { -private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another permanent you control"); +private static final FilterControlledPermanent filter = new FilterControlledPermanent("another permanent you control"); static { filter.add(AnotherPredicate.instance); } - public SaltRoadAmbushersTriggeredAbility() { + SaltRoadAmbushersTriggeredAbility() { super(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), filter, true); } @@ -71,4 +70,4 @@ private static final FilterControlledCreaturePermanent filter = new FilterContro public String getRule() { return "Whenever another permanent you control is turned face up, if it's a creature, put two +1/+1 counters on it."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SamiteElder.java b/Mage.Sets/src/mage/cards/s/SamiteElder.java index af5098f2442..bc291e0b931 100644 --- a/Mage.Sets/src/mage/cards/s/SamiteElder.java +++ b/Mage.Sets/src/mage/cards/s/SamiteElder.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -18,7 +17,7 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -70,12 +69,12 @@ class SamiteElderEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent target = game.getPermanent(source.getFirstTarget()); - if(target != null) { - for(ObjectColor color : target.getColor(game).getColors()) { + if (target != null) { + for (ObjectColor color : target.getColor(game).getColors()) { FilterCard filter = new FilterCard(color.getDescription()); filter.add(new ColorPredicate(color)); game.addEffect(new GainAbilityControlledEffect(new ProtectionAbility(filter), - Duration.EndOfTurn, new FilterControlledCreaturePermanent()), source); + Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES), source); } return true; } diff --git a/Mage.Sets/src/mage/cards/s/SamiteMinistration.java b/Mage.Sets/src/mage/cards/s/SamiteMinistration.java index 3a15cf149e7..461c693749e 100644 --- a/Mage.Sets/src/mage/cards/s/SamiteMinistration.java +++ b/Mage.Sets/src/mage/cards/s/SamiteMinistration.java @@ -62,6 +62,7 @@ class SamiteMinistrationEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/s/SandsOfTime.java b/Mage.Sets/src/mage/cards/s/SandsOfTime.java index 8bfc61cf168..a27a4bc7bdb 100644 --- a/Mage.Sets/src/mage/cards/s/SandsOfTime.java +++ b/Mage.Sets/src/mage/cards/s/SandsOfTime.java @@ -72,9 +72,9 @@ class SandsOfTimeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, targetPointer.getFirst(game, source), game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, getTargetPointer().getFirst(game, source), game)) { if (permanent.isTapped()) { permanent.untap(game); } else { diff --git a/Mage.Sets/src/mage/cards/s/SandstoneNeedle.java b/Mage.Sets/src/mage/cards/s/SandstoneNeedle.java index db59c59fc89..f4c019a1bd8 100644 --- a/Mage.Sets/src/mage/cards/s/SandstoneNeedle.java +++ b/Mage.Sets/src/mage/cards/s/SandstoneNeedle.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,8 +30,11 @@ public final class SandstoneNeedle extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); // Sandstone Needle enters the battlefield tapped with two depletion counters on it. - this.addAbility(new EntersBattlefieldTappedAbility()); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2)))); + Ability etbAbility = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with two depletion counters on it" + ); + etbAbility.addEffect(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2))); + this.addAbility(etbAbility); // {tap}, Remove a depletion counter from Sandstone Needle: Add {R}{R}. If there are no depletion counters on Sandstone Needle, sacrifice it. Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(2), new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.DEPLETION.createInstance(1))); diff --git a/Mage.Sets/src/mage/cards/s/SanitationAutomaton.java b/Mage.Sets/src/mage/cards/s/SanitationAutomaton.java index ecd39d6e02c..86164bcaf03 100644 --- a/Mage.Sets/src/mage/cards/s/SanitationAutomaton.java +++ b/Mage.Sets/src/mage/cards/s/SanitationAutomaton.java @@ -16,7 +16,7 @@ import java.util.UUID; public final class SanitationAutomaton extends CardImpl { public SanitationAutomaton(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, ""); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); this.subtype.add(SubType.CONSTRUCT); this.power = new MageInt(2); diff --git a/Mage.Sets/src/mage/cards/s/SaprazzanSkerry.java b/Mage.Sets/src/mage/cards/s/SaprazzanSkerry.java index fa60c60d6ca..ba09afc094f 100644 --- a/Mage.Sets/src/mage/cards/s/SaprazzanSkerry.java +++ b/Mage.Sets/src/mage/cards/s/SaprazzanSkerry.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,8 +30,11 @@ public final class SaprazzanSkerry extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); // Saprazzan Skerry enters the battlefield tapped with two depletion counters on it. - this.addAbility(new EntersBattlefieldTappedAbility()); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2)))); + Ability etbAbility = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with two depletion counters on it" + ); + etbAbility.addEffect(new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(2))); + this.addAbility(etbAbility); // {tap}, Remove a depletion counter from Saprazzan Skerry: Add {U}{U}. If there are no depletion counters on Saprazzan Skerry, sacrifice it. Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(2), new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.DEPLETION.createInstance(1))); diff --git a/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java b/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java index f712d2743be..88ba634a00e 100644 --- a/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java +++ b/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java @@ -73,7 +73,7 @@ class SarkhanTheMasterlessDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SarkhansRage.java b/Mage.Sets/src/mage/cards/s/SarkhansRage.java index 1bb54faf47d..be1c511dbc4 100644 --- a/Mage.Sets/src/mage/cards/s/SarkhansRage.java +++ b/Mage.Sets/src/mage/cards/s/SarkhansRage.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -11,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetAnyTarget; /** @@ -27,7 +26,7 @@ public final class SarkhansRage extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(5)); this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new DamageControllerEffect(2), - new PermanentsOnTheBattlefieldCondition(new FilterControlledCreaturePermanent(SubType.DRAGON,"you control no Dragons"), ComparisonType.EQUAL_TO, 0) + new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.DRAGON,"you control no Dragons"), ComparisonType.EQUAL_TO, 0) )); } diff --git a/Mage.Sets/src/mage/cards/s/SavageSummoning.java b/Mage.Sets/src/mage/cards/s/SavageSummoning.java index e10ba2dd44b..ac293c3135a 100644 --- a/Mage.Sets/src/mage/cards/s/SavageSummoning.java +++ b/Mage.Sets/src/mage/cards/s/SavageSummoning.java @@ -72,6 +72,7 @@ class SavageSummoningAsThoughEffect extends AsThoughEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); watcher = game.getState().getWatcher(SavageSummoningWatcher.class, source.getControllerId()); Card card = game.getCard(source.getSourceId()); if (watcher != null && card != null) { @@ -188,6 +189,7 @@ class SavageSummoningCantCounterEffect extends ContinuousRuleModifyingEffectImpl @Override public void init(Ability source, Game game) { + super.init(source, game); watcher = game.getState().getWatcher(SavageSummoningWatcher.class, source.getControllerId()); Card card = game.getCard(source.getSourceId()); if (watcher == null || card == null) { @@ -244,6 +246,7 @@ class SavageSummoningEntersBattlefieldEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); watcher = game.getState().getWatcher(SavageSummoningWatcher.class, source.getControllerId()); Card card = game.getCard(source.getSourceId()); if (watcher == null || card == null) { diff --git a/Mage.Sets/src/mage/cards/s/SavageThallid.java b/Mage.Sets/src/mage/cards/s/SavageThallid.java index a74071d99b3..6ec0efb5c06 100644 --- a/Mage.Sets/src/mage/cards/s/SavageThallid.java +++ b/Mage.Sets/src/mage/cards/s/SavageThallid.java @@ -19,10 +19,9 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -36,11 +35,7 @@ public final class SavageThallid extends CardImpl { filter.add(SubType.FUNGUS.getPredicate()); } - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("Saproling"); - - static { - filter2.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.SAPROLING, "Saproling"); public SavageThallid(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java b/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java index 9f8579e89d1..8b57dfea709 100644 --- a/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java +++ b/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java @@ -13,12 +13,15 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; +import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; import mage.util.CardUtil; @@ -86,11 +89,27 @@ class SaviorOfOllenbockTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TRAINED_CREATURE; + return event.getType() == GameEvent.EventType.COUNTER_ADDED; } @Override public boolean checkTrigger(GameEvent event, Game game) { + // 20240202 - 702.149c + // Some creatures with training have abilities that trigger when they train. + // "When this creature trains" means "When a resolving training ability puts a +1/+1 counter on this creature." + if (!event.getData().equals(CounterType.P1P1.getName())) { + return false; + } + + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + if (!(stackObject instanceof StackAbility)) { + return false; + } + Ability ability = stackObject.getStackAbility(); + if (!(ability instanceof TrainingAbility)) { + return false; + } + return event.getTargetId().equals(getSourceId()); } diff --git a/Mage.Sets/src/mage/cards/s/ScamperingScorcher.java b/Mage.Sets/src/mage/cards/s/ScamperingScorcher.java index 9561b437f8a..ad62a54162a 100644 --- a/Mage.Sets/src/mage/cards/s/ScamperingScorcher.java +++ b/Mage.Sets/src/mage/cards/s/ScamperingScorcher.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.RedElementalToken; import java.util.UUID; @@ -23,7 +23,7 @@ import java.util.UUID; public final class ScamperingScorcher extends CardImpl { private static final FilterPermanent filter - = new FilterControlledCreaturePermanent(SubType.ELEMENTAL, "Elementals"); + = new FilterControlledPermanent(SubType.ELEMENTAL, "Elementals"); public ScamperingScorcher(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); diff --git a/Mage.Sets/src/mage/cards/s/Scarecrone.java b/Mage.Sets/src/mage/cards/s/Scarecrone.java index 7cd461b4c74..96be5665410 100644 --- a/Mage.Sets/src/mage/cards/s/Scarecrone.java +++ b/Mage.Sets/src/mage/cards/s/Scarecrone.java @@ -16,9 +16,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledCreaturePermanent; /** * @author Loki @@ -26,7 +25,7 @@ import mage.target.common.TargetControlledCreaturePermanent; public final class Scarecrone extends CardImpl { private static final FilterCard filter = new FilterCard("artifact creature card from your graveyard"); - private static final FilterControlledCreaturePermanent filterScarecrow = new FilterControlledCreaturePermanent("Scarecrow"); + private static final FilterControlledPermanent filterScarecrow = new FilterControlledPermanent("Scarecrow"); static { filter.add(CardType.ARTIFACT.getPredicate()); filter.add(CardType.CREATURE.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/s/ScarwoodBandits.java b/Mage.Sets/src/mage/cards/s/ScarwoodBandits.java index 071f7bc9e34..92b01a83543 100644 --- a/Mage.Sets/src/mage/cards/s/ScarwoodBandits.java +++ b/Mage.Sets/src/mage/cards/s/ScarwoodBandits.java @@ -129,7 +129,7 @@ class DoUnlessAnyOpponentPaysEffect extends OneShotEffect { // do the effects if nobody paid if (doEffect) { for (Effect effect : executingEffects) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { result &= effect.apply(game, source); } else { diff --git a/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java b/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java index 8f473cbc245..aadfea87a73 100644 --- a/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java +++ b/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java @@ -95,7 +95,6 @@ class ScholarOfTheLostTroveEffect extends OneShotEffect { if (!controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getLogName() + '?', source, game)) { return true; } - FixedTarget fixedTarget = new FixedTarget(card, game); game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); @@ -104,7 +103,7 @@ class ScholarOfTheLostTroveEffect extends OneShotEffect { return true; } ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(true); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(new FixedTarget(card, game)); effect.setText("If an instant or sorcery spell cast this way would be put into your graveyard this turn, exile it instead"); game.addEffect(effect, source); return true; diff --git a/Mage.Sets/src/mage/cards/s/ScoutsWarning.java b/Mage.Sets/src/mage/cards/s/ScoutsWarning.java index 348ae3a4fa8..0d5f4d90b6b 100644 --- a/Mage.Sets/src/mage/cards/s/ScoutsWarning.java +++ b/Mage.Sets/src/mage/cards/s/ScoutsWarning.java @@ -63,6 +63,7 @@ class ScoutsWarningAsThoughEffect extends AsThoughEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); ScoutsWarningWatcher watcher = game.getState().getWatcher(ScoutsWarningWatcher.class, source.getControllerId()); Card card = game.getCard(source.getSourceId()); if (watcher != null && card != null) { diff --git a/Mage.Sets/src/mage/cards/s/ScrollOfFate.java b/Mage.Sets/src/mage/cards/s/ScrollOfFate.java index 0d467c260f9..0f42f901154 100644 --- a/Mage.Sets/src/mage/cards/s/ScrollOfFate.java +++ b/Mage.Sets/src/mage/cards/s/ScrollOfFate.java @@ -1,28 +1,20 @@ package mage.cards.s; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.cards.Card; +import mage.abilities.effects.keyword.ManifestEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInHand; -import java.util.Objects; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -66,42 +58,15 @@ class ScrollOfFateEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller == null - || controller.getHand().isEmpty()) { + if (controller == null) { return false; } + TargetCardInHand targetCard = new TargetCardInHand(); - if (!controller.chooseTarget(Outcome.PutCardInPlay, controller.getHand(), - targetCard, source, game)) { + if (!controller.chooseTarget(Outcome.PutCardInPlay, controller.getHand(), targetCard, source, game)) { return false; } - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - Set cards = targetCard - .getTargets() - .stream() - .map(game::getCard) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - cards.stream().forEach(card -> { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), - card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, - Duration.Custom, BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED), newSource); - }); - controller.moveCards(cards, Zone.BATTLEFIELD, source, game, false, true, false, null); - cards.stream() - .map(Card::getId) - .map(game::getPermanent) - .filter(permanent -> permanent != null) - .forEach(permanent -> permanent.setManifested(true)); - return true; + + return ManifestEffect.doManifestCards(game, source, controller, new CardsImpl(targetCard.getTargets()).getCards(game)); } } diff --git a/Mage.Sets/src/mage/cards/s/Scrounge.java b/Mage.Sets/src/mage/cards/s/Scrounge.java index cd1f55c4517..49cbe913724 100644 --- a/Mage.Sets/src/mage/cards/s/Scrounge.java +++ b/Mage.Sets/src/mage/cards/s/Scrounge.java @@ -58,7 +58,7 @@ class ScroungeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && opponent != null) { FilterArtifactCard filter = new FilterArtifactCard(); filter.add(new OwnerIdPredicate(opponent.getId())); diff --git a/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java b/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java new file mode 100644 index 00000000000..638410f2b4a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.hint.common.CreaturesYouControlHint; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.game.permanent.token.Gremlin11Token; +import mage.game.permanent.token.GremlinToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScurryOfGremlins extends CardImpl { + + public ScurryOfGremlins(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}{W}"); + + // When Scurry of Gremlins enters the battlefield, create two 1/1 red Gremlin creature tokens. Then you get an amount of {E} equal to the number of creatures you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new Gremlin11Token(), 2)); + ability.addEffect(new GetEnergyCountersControllerEffect(CreaturesYouControlCount.instance) + .setText("Then you get an amount of {E} equal to the number of creatures you control")); + this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); + + // Pay {E}{E}{E}{E}: Creatures you control get +1/+0 and gain haste until end of turn. + ability = new SimpleActivatedAbility(new BoostControlledEffect( + 1, 0, Duration.EndOfTurn + ).setText("creatures you control get +1/+0"), new PayEnergyCost(4)); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain haste until end of turn")); + this.addAbility(ability); + } + + private ScurryOfGremlins(final ScurryOfGremlins card) { + super(card); + } + + @Override + public ScurryOfGremlins copy() { + return new ScurryOfGremlins(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScuttlingSentinel.java b/Mage.Sets/src/mage/cards/s/ScuttlingSentinel.java new file mode 100644 index 00000000000..177334775fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScuttlingSentinel.java @@ -0,0 +1,66 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect; +import mage.abilities.effects.common.continuous.BecomesColorTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class ScuttlingSentinel extends CardImpl { + + public ScuttlingSentinel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G/U}{G/U}"); + + this.subtype.add(SubType.CRAB); + this.subtype.add(SubType.ELF); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Scuttling Sentinel enters the battlefield, put a +1/+1 counter on another target creature you control. + // Until end of turn, that creature becomes a blue Crab in addition to its other types and gains hexproof. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + ability.addEffect(new BecomesColorTargetEffect(ObjectColor.BLUE, Duration.EndOfTurn) + .setText("until end of turn, that creature becomes a blue")); + ability.addEffect(new AddCardSubTypeTargetEffect(SubType.CRAB, Duration.EndOfTurn) + .setText(" Crab in addition to its other types")); + ability.addEffect(new GainAbilityTargetEffect(HexproofAbility.getInstance()) + .setText("and gains hexproof")); + this.addAbility(ability); + + } + + private ScuttlingSentinel(final ScuttlingSentinel card) { + super(card); + } + + @Override + public ScuttlingSentinel copy() { + return new ScuttlingSentinel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeaGateLoremaster.java b/Mage.Sets/src/mage/cards/s/SeaGateLoremaster.java index f1bd30dbdd1..da1c02460cd 100644 --- a/Mage.Sets/src/mage/cards/s/SeaGateLoremaster.java +++ b/Mage.Sets/src/mage/cards/s/SeaGateLoremaster.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -12,7 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -20,11 +19,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class SeaGateLoremaster extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "Ally you control"); public SeaGateLoremaster(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{U}"); diff --git a/Mage.Sets/src/mage/cards/s/SeasonsBeatings.java b/Mage.Sets/src/mage/cards/s/SeasonsBeatings.java index 36b50d4d36b..9448e5da0ad 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonsBeatings.java +++ b/Mage.Sets/src/mage/cards/s/SeasonsBeatings.java @@ -56,7 +56,7 @@ class SeasonsBeatingsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { Map creatures = new HashMap<>(); int numCreature = 0; diff --git a/Mage.Sets/src/mage/cards/s/SeizeTheInitiative.java b/Mage.Sets/src/mage/cards/s/SeizeTheInitiative.java index 5a71a144ce7..66bc1fbc9a0 100644 --- a/Mage.Sets/src/mage/cards/s/SeizeTheInitiative.java +++ b/Mage.Sets/src/mage/cards/s/SeizeTheInitiative.java @@ -21,8 +21,11 @@ public final class SeizeTheInitiative extends CardImpl { public SeizeTheInitiative (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{W}"); - this.getSpellAbility().addEffect(new BoostTargetEffect(1, 1, Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn)); + // Target creature gets +1/+1 and gains first strike until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 1, Duration.EndOfTurn) + .setText("target creature gets +1/+1")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn) + .setText("and gains first strike until end of turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/s/SeizeTheSpotlight.java b/Mage.Sets/src/mage/cards/s/SeizeTheSpotlight.java index 8c5d5a8b8f5..7e6afa95111 100644 --- a/Mage.Sets/src/mage/cards/s/SeizeTheSpotlight.java +++ b/Mage.Sets/src/mage/cards/s/SeizeTheSpotlight.java @@ -2,8 +2,9 @@ package mage.cards.s; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainControlAllEffect; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -13,12 +14,12 @@ import mage.constants.Outcome; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetpointer.FixedTargets; import java.util.ArrayList; import java.util.List; @@ -92,7 +93,7 @@ class SeizeTheSpotlightEffect extends OneShotEffect { filter.add(new ControllerIdPredicate(opponent.getId())); TargetPermanent target = new TargetPermanent(filter); target.withNotTarget(true); - if (!target.canChoose(source.getSourceId(), source, game)) { + if (!target.canChoose(controller.getId(), source, game)) { continue; } controller.choose(outcome, target, source, game); @@ -100,14 +101,15 @@ class SeizeTheSpotlightEffect extends OneShotEffect { if (permanent == null) { continue; } - permanent.untap(game); permanents.add(permanent); } if (!permanents.isEmpty()) { - game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn) - .setTargetPointer(new FixedTargets(permanents, game)), source); - game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) - .setTargetPointer(new FixedTargets(permanents, game)), source); + FilterPermanent affectedFilter = new FilterPermanent(); + affectedFilter.add(new PermanentReferenceInCollectionPredicate(permanents, game)); + new GainControlAllEffect(Duration.EndOfTurn, affectedFilter).apply(game, source); + game.getState().processAction(game); + new UntapAllEffect(affectedFilter).apply(game, source); + game.addEffect(new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, affectedFilter), source); } if (fortune > 0) { controller.drawCards(fortune, source, game); diff --git a/Mage.Sets/src/mage/cards/s/SekkiSeasonsGuide.java b/Mage.Sets/src/mage/cards/s/SekkiSeasonsGuide.java index ed2bce8e7ab..1b7b3b1476c 100644 --- a/Mage.Sets/src/mage/cards/s/SekkiSeasonsGuide.java +++ b/Mage.Sets/src/mage/cards/s/SekkiSeasonsGuide.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -16,12 +15,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.SpiritToken; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSacrifice; /** @@ -30,11 +28,7 @@ import mage.target.common.TargetSacrifice; */ public final class SekkiSeasonsGuide extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Spirits"); - - static { - filter.add(SubType.SPIRIT.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SPIRIT, "Spirits"); public SekkiSeasonsGuide(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{G}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/s/SelflessExorcist.java b/Mage.Sets/src/mage/cards/s/SelflessExorcist.java index 6461f6bf3a3..79e11cd257c 100644 --- a/Mage.Sets/src/mage/cards/s/SelflessExorcist.java +++ b/Mage.Sets/src/mage/cards/s/SelflessExorcist.java @@ -13,6 +13,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.Permanent; @@ -26,8 +27,6 @@ import java.util.UUID; */ public final class SelflessExorcist extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public SelflessExorcist(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); @@ -38,7 +37,7 @@ public final class SelflessExorcist extends CardImpl { // {tap}: Exile target creature card from a graveyard. That card deals damage equal to its power to Selfless Exorcist. Ability ability = new SimpleActivatedAbility(new SelflessExorcistEffect(), new TapSourceCost()); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SemestersEnd.java b/Mage.Sets/src/mage/cards/s/SemestersEnd.java index 1cd697622a6..17f883bec29 100644 --- a/Mage.Sets/src/mage/cards/s/SemestersEnd.java +++ b/Mage.Sets/src/mage/cards/s/SemestersEnd.java @@ -79,7 +79,7 @@ class SemestersEndEffect extends OneShotEffect { if (player == null) { return false; } - Cards cards = new CardsImpl(targetPointer.getTargets(game, source)); + Cards cards = new CardsImpl(getTargetPointer().getTargets(game, source)); player.moveCards(cards, Zone.EXILED, source, game); cards.retainZone(Zone.EXILED, game); game.addDelayedTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/s/SenatorPeacock.java b/Mage.Sets/src/mage/cards/s/SenatorPeacock.java index 24dec4fd9fc..5a0f5aff4c7 100644 --- a/Mage.Sets/src/mage/cards/s/SenatorPeacock.java +++ b/Mage.Sets/src/mage/cards/s/SenatorPeacock.java @@ -15,7 +15,6 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -25,8 +24,6 @@ import java.util.UUID; */ public final class SenatorPeacock extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CLUE, "a Clue"); - public SenatorPeacock(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); @@ -48,7 +45,7 @@ public final class SenatorPeacock extends CardImpl { this.addAbility(ability); // Whenever you sacrifice a Clue, target creature can't be blocked this turn. - ability = new SacrificePermanentTriggeredAbility(new CantBeBlockedTargetEffect(), filter); + ability = new SacrificePermanentTriggeredAbility(new CantBeBlockedTargetEffect(), StaticFilters.FILTER_CONTROLLED_CLUE); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/Seraph.java b/Mage.Sets/src/mage/cards/s/Seraph.java index 813c9652959..8b552d90b92 100644 --- a/Mage.Sets/src/mage/cards/s/Seraph.java +++ b/Mage.Sets/src/mage/cards/s/Seraph.java @@ -74,7 +74,7 @@ class SeraphEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card creatureCard = game.getCard(targetPointer.getFirst(game, source)); + Card creatureCard = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && creatureCard != null && game.getState().getZone(creatureCard.getId()) == Zone.GRAVEYARD) { // must be still in the graveyard diff --git a/Mage.Sets/src/mage/cards/s/SereneMaster.java b/Mage.Sets/src/mage/cards/s/SereneMaster.java index a3310a63748..380d30a4f0d 100644 --- a/Mage.Sets/src/mage/cards/s/SereneMaster.java +++ b/Mage.Sets/src/mage/cards/s/SereneMaster.java @@ -77,7 +77,7 @@ class SereneMasterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent sourceCreature = game.getPermanent(source.getSourceId()); - Permanent attackingCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent attackingCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (sourceCreature != null && attackingCreature != null) { StaticValue newSourcePower = StaticValue.get(attackingCreature.getPower().getValue()); StaticValue newAttackerPower = StaticValue.get(sourceCreature.getPower().getValue()); diff --git a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java index 0210def2ebb..bead45953c7 100644 --- a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java +++ b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java @@ -77,7 +77,7 @@ class SerpentsSoulJarExileEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Permanent permanent = source.getSourcePermanentIfItStillExists(game); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (player == null || permanent == null || card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SerratedArrows.java b/Mage.Sets/src/mage/cards/s/SerratedArrows.java index b75ddba0bb2..a3703e2d581 100644 --- a/Mage.Sets/src/mage/cards/s/SerratedArrows.java +++ b/Mage.Sets/src/mage/cards/s/SerratedArrows.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; @@ -9,7 +7,7 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -22,6 +20,8 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -35,10 +35,14 @@ public final class SerratedArrows extends CardImpl { Effect effect = new AddCountersSourceEffect(CounterType.ARROWHEAD.createInstance(3)); effect.setText("with three arrowhead counters on it"); this.addAbility(new EntersBattlefieldAbility(effect)); + // At the beginning of your upkeep, if there are no arrowhead counters on Serrated Arrows, sacrifice it. - effect = new ConditionalOneShotEffect(new SacrificeSourceEffect(), new SourceHasCounterCondition(CounterType.ARROWHEAD, 0, 0), - "if there are no arrowhead counters on {this}, sacrifice it"); - this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, effect, TargetController.YOU, false, false)); + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new SacrificeSourceEffect(), TargetController.YOU, false), + new SourceHasCounterCondition(CounterType.ARROWHEAD, 0, 0), + "At the beginning of your upkeep, if there are no arrowhead counters on {this}, sacrifice it" + )); + // {T}, Remove an arrowhead counter from Serrated Arrows: Put a -1/-1 counter on target creature. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.M1M1.createInstance()), diff --git a/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java b/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java index 3f428012bdb..8ecde4bfcbb 100644 --- a/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java +++ b/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java @@ -81,7 +81,7 @@ class ServantOfTheScaleEffect extends OneShotEffect { int amount = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); if (amount > 0) { Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(amount)); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/s/SetonKrosanProtector.java b/Mage.Sets/src/mage/cards/s/SetonKrosanProtector.java index 4a64671bd98..94a1bc453f6 100644 --- a/Mage.Sets/src/mage/cards/s/SetonKrosanProtector.java +++ b/Mage.Sets/src/mage/cards/s/SetonKrosanProtector.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.ArrayList; @@ -17,16 +16,21 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; /** * * @author cbt33 */ public final class SetonKrosanProtector extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DRUID, "untapped Druid you control"); + static { + filter.add(TappedPredicate.UNTAPPED); + } public SetonKrosanProtector(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}{G}{G}"); @@ -38,13 +42,10 @@ public final class SetonKrosanProtector extends CardImpl { this.toughness = new MageInt(2); // Tap an untapped Druid you control: Add {G}. - FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Druid you control"); - filter.add(TappedPredicate.UNTAPPED); - filter.add(SubType.DRUID.getPredicate()); this.addAbility(new SimpleManaAbility( Zone.BATTLEFIELD, new SetonKrosanProtectorManaEffect(filter), - new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, true)))); + new TapTargetCost(new TargetControlledPermanent(1, 1, filter, true)))); } private SetonKrosanProtector(final SetonKrosanProtector card) { @@ -61,7 +62,7 @@ class SetonKrosanProtectorManaEffect extends BasicManaEffect { private final FilterPermanent filter; - public SetonKrosanProtectorManaEffect(FilterPermanent filter) { + SetonKrosanProtectorManaEffect(FilterPermanent filter) { super(Mana.GreenMana(1)); this.filter = filter; } diff --git a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java index dd23f9e0e1c..1bdb1a4c288 100644 --- a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java +++ b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java @@ -65,7 +65,7 @@ class SeverTheBloodlineEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (controller != null && targetPermanent != null) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); if (CardUtil.haveEmptyName(targetPermanent)) { diff --git a/Mage.Sets/src/mage/cards/s/ShadowKin.java b/Mage.Sets/src/mage/cards/s/ShadowKin.java index 0f4fc8fa159..95253a8f555 100644 --- a/Mage.Sets/src/mage/cards/s/ShadowKin.java +++ b/Mage.Sets/src/mage/cards/s/ShadowKin.java @@ -99,7 +99,6 @@ class ShadowKinEffect extends OneShotEffect { CopyApplier applier = new ShadowKinApplier(); applier.apply(game, blueprint, source, sourcePermanent.getId()); CopyEffect copyEffect = new CopyEffect(Duration.Custom, blueprint, sourcePermanent.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/s/ShadowPuppeteers.java b/Mage.Sets/src/mage/cards/s/ShadowPuppeteers.java index 61cc82b38cd..436b0f948ca 100644 --- a/Mage.Sets/src/mage/cards/s/ShadowPuppeteers.java +++ b/Mage.Sets/src/mage/cards/s/ShadowPuppeteers.java @@ -97,7 +97,7 @@ class ShadowPuppeteersContinousEffect extends ContinuousEffectImpl { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/ShallowGrave.java b/Mage.Sets/src/mage/cards/s/ShallowGrave.java index ac282a59052..1d316598ec0 100644 --- a/Mage.Sets/src/mage/cards/s/ShallowGrave.java +++ b/Mage.Sets/src/mage/cards/s/ShallowGrave.java @@ -76,14 +76,14 @@ class ShallowGraveEffect extends OneShotEffect { if (controller.moveCards(lastCreatureCard, Zone.BATTLEFIELD, source, game)) { Permanent returnedCreature = game.getPermanent(lastCreatureCard.getId()); if (returnedCreature != null) { - FixedTarget fixedTarget = new FixedTarget(returnedCreature, game); + FixedTarget blueprintTarget = new FixedTarget(returnedCreature, game); // Gains Haste ContinuousEffect hasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - hasteEffect.setTargetPointer(fixedTarget); + hasteEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(hasteEffect, source); // Exile it at end of turn ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD); - exileEffect.setTargetPointer(fixedTarget); + exileEffect.setTargetPointer(blueprintTarget.copy()); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect); game.addDelayedTriggeredAbility(delayedAbility, source); } diff --git a/Mage.Sets/src/mage/cards/s/ShamanEnKor.java b/Mage.Sets/src/mage/cards/s/ShamanEnKor.java index e20a23c458b..1d3bfe3eee6 100644 --- a/Mage.Sets/src/mage/cards/s/ShamanEnKor.java +++ b/Mage.Sets/src/mage/cards/s/ShamanEnKor.java @@ -83,6 +83,7 @@ class ShamanEnKorRedirectFromTargetEffect extends RedirectionEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); Player player = game.getPlayer(source.getControllerId()); if (player != null) { TargetSource target = new TargetSource(); diff --git a/Mage.Sets/src/mage/cards/s/ShambleBack.java b/Mage.Sets/src/mage/cards/s/ShambleBack.java index 76cf7ad4288..97a4d734b94 100644 --- a/Mage.Sets/src/mage/cards/s/ShambleBack.java +++ b/Mage.Sets/src/mage/cards/s/ShambleBack.java @@ -8,6 +8,7 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.permanent.token.ZombieToken; import mage.target.common.TargetCardInGraveyard; @@ -23,7 +24,7 @@ public final class ShambleBack extends CardImpl { // Exile target creature card from a graveyard. Create a 2/2 black Zombie creature token. You gain 2 life. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken())); this.getSpellAbility().addEffect(new GainLifeEffect(2)); } diff --git a/Mage.Sets/src/mage/cards/s/SharpEyedRookie.java b/Mage.Sets/src/mage/cards/s/SharpEyedRookie.java new file mode 100644 index 00000000000..f279b4958bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SharpEyedRookie.java @@ -0,0 +1,84 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SharpEyedRookie extends CardImpl { + + public SharpEyedRookie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever a creature enters the battlefield under your control, if its power is greater than Sharp-Eyed Rookie's power or its toughness is greater than Sharp-Eyed Rookie's toughness, put a +1/+1 counter on Sharp-Eyed Rookie and investigate. + this.addAbility(new SharpEyedRookieTriggeredAbility()); + } + + private SharpEyedRookie(final SharpEyedRookie card) { + super(card); + } + + @Override + public SharpEyedRookie copy() { + return new SharpEyedRookie(this); + } +} + +class SharpEyedRookieTriggeredAbility extends EntersBattlefieldAllTriggeredAbility { + + SharpEyedRookieTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), StaticFilters.FILTER_CONTROLLED_CREATURE, false); + this.addEffect(new InvestigateEffect().concatBy("and")); + setTriggerPhrase("Whenever a creature enters the battlefield under your control, " + + "if its power is greater than {this}'s power or its toughness is greater than {this}'s toughness, "); + } + + private SharpEyedRookieTriggeredAbility(final SharpEyedRookieTriggeredAbility ability) { + super(ability); + } + + @Override + public SharpEyedRookieTriggeredAbility copy() { + return new SharpEyedRookieTriggeredAbility(this); + } + + @Override + public boolean checkInterveningIfClause(Game game) { + Permanent sourcePermanent = getSourcePermanentOrLKI(game); + Permanent permanentEntering = (Permanent) this + .getEffects() + .stream() + .map(effect -> effect.getValue("permanentEnteringBattlefield")) + .findFirst() + .orElse(null); + return sourcePermanent != null + && permanentEntering != null + && sourcePermanent.isCreature(game) + && permanentEntering.isCreature(game) + && (permanentEntering.getPower().getValue() > sourcePermanent.getPower().getValue() + || permanentEntering.getToughness().getValue() > sourcePermanent.getToughness().getValue()); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java index 143b9ea9808..c6b7ad43c21 100644 --- a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java +++ b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java @@ -93,7 +93,7 @@ class ShellOfTheLastKappaEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent == null) { diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 5ec50d2f6ed..4d32904e1e6 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -207,7 +207,7 @@ class ShelobChildOfUngoliantEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent copyFrom = targetPointer.getFirstTargetPermanentOrLKI(game, source); + Permanent copyFrom = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if(controller == null || copyFrom == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/ShelobDreadWeaver.java b/Mage.Sets/src/mage/cards/s/ShelobDreadWeaver.java index 751838486e8..9bed8776f54 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobDreadWeaver.java +++ b/Mage.Sets/src/mage/cards/s/ShelobDreadWeaver.java @@ -103,7 +103,7 @@ class ShelobDreadWeaverExileEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Permanent permanent = source.getSourcePermanentIfItStillExists(game); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (player == null || permanent == null || card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/ShieldmageAdvocate.java b/Mage.Sets/src/mage/cards/s/ShieldmageAdvocate.java index 11602fecb74..7816b5e1222 100644 --- a/Mage.Sets/src/mage/cards/s/ShieldmageAdvocate.java +++ b/Mage.Sets/src/mage/cards/s/ShieldmageAdvocate.java @@ -82,6 +82,7 @@ class ShieldmageAdvocateEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @@ -94,7 +95,7 @@ class ShieldmageAdvocateEffect extends PreventionEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (super.applies(event, source, game)) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) { + if (event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) { return true; } } diff --git a/Mage.Sets/src/mage/cards/s/ShimmerDragon.java b/Mage.Sets/src/mage/cards/s/ShimmerDragon.java index 8847c72505c..9ae87af2f08 100644 --- a/Mage.Sets/src/mage/cards/s/ShimmerDragon.java +++ b/Mage.Sets/src/mage/cards/s/ShimmerDragon.java @@ -7,8 +7,10 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; @@ -55,7 +57,7 @@ public final class ShimmerDragon extends CardImpl { new GainAbilitySourceEffect( HexproofAbility.getInstance(), Duration.WhileOnBattlefield ), condition, "as long as you control four or more artifacts, {this} has hexproof" - ))); + )).addHint(new ValueHint("Artifacts you control", new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT)))); // Tap two untapped artifacts you control: Draw a card. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/s/ShiningShoal.java b/Mage.Sets/src/mage/cards/s/ShiningShoal.java index cd11a7df768..10f1f3aca49 100644 --- a/Mage.Sets/src/mage/cards/s/ShiningShoal.java +++ b/Mage.Sets/src/mage/cards/s/ShiningShoal.java @@ -86,6 +86,7 @@ class ShiningShoalRedirectDamageTargetEffect extends RedirectDamageFromSourceToT @Override public void init(Ability source, Game game) { + super.init(source, game); amountToRedirect = dynamicAmount.calculate(game, source, this); } diff --git a/Mage.Sets/src/mage/cards/s/Shocker.java b/Mage.Sets/src/mage/cards/s/Shocker.java index 71c7b47ff62..1d3c0335bd9 100644 --- a/Mage.Sets/src/mage/cards/s/Shocker.java +++ b/Mage.Sets/src/mage/cards/s/Shocker.java @@ -57,7 +57,7 @@ class ShockerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/ShowOfDominance.java b/Mage.Sets/src/mage/cards/s/ShowOfDominance.java index 593aee6aa9e..0ec001fb5a4 100644 --- a/Mage.Sets/src/mage/cards/s/ShowOfDominance.java +++ b/Mage.Sets/src/mage/cards/s/ShowOfDominance.java @@ -90,14 +90,14 @@ class ShowOfDominanceEffect extends OneShotEffect { } } if (selectedCreature != null) { - FixedTarget target = new FixedTarget(selectedCreature.getId(), game); + FixedTarget blueprintTarget = new FixedTarget(selectedCreature.getId(), game); Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(4)); - effect.setTargetPointer(target); + effect.setTargetPointer(blueprintTarget.copy()); effect.apply(game, source); ContinuousEffect continuousEffect = new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn); - continuousEffect.setTargetPointer(target); + continuousEffect.setTargetPointer(blueprintTarget.copy()); game.addEffect(continuousEffect, source); return true; } diff --git a/Mage.Sets/src/mage/cards/s/ShowstoppingSurprise.java b/Mage.Sets/src/mage/cards/s/ShowstoppingSurprise.java new file mode 100644 index 00000000000..c39f7e48dc6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShowstoppingSurprise.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShowstoppingSurprise extends CardImpl { + + public ShowstoppingSurprise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R}{R}"); + + // Choose target creature you control. Turn it face up if it's face down. Then it deals damage equal to its power to each other creature. + this.getSpellAbility().addEffect(new ShowstoppingSurpriseEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + } + + private ShowstoppingSurprise(final ShowstoppingSurprise card) { + super(card); + } + + @Override + public ShowstoppingSurprise copy() { + return new ShowstoppingSurprise(this); + } +} + +class ShowstoppingSurpriseEffect extends OneShotEffect { + + ShowstoppingSurpriseEffect() { + super(Outcome.Benefit); + staticText = "choose target creature you control. Turn it face up if it's face down. " + + "Then it deals damage equal to its power to each other creature"; + } + + private ShowstoppingSurpriseEffect(final ShowstoppingSurpriseEffect effect) { + super(effect); + } + + @Override + public ShowstoppingSurpriseEffect copy() { + return new ShowstoppingSurpriseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + if (permanent.isFaceDown(game)) { + permanent.turnFaceUp(source, game, source.getControllerId()); + game.applyEffects(); + } + int power = permanent.getPower().getValue(); + if (power < 1) { + return true; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getControllerId(), source, game + )) { + if (!creature.getId().equals(permanent.getId())) { + creature.damage(power, permanent.getId(), source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShredsOfSanity.java b/Mage.Sets/src/mage/cards/s/ShredsOfSanity.java index 7ec252f326a..d2b8c8b734e 100644 --- a/Mage.Sets/src/mage/cards/s/ShredsOfSanity.java +++ b/Mage.Sets/src/mage/cards/s/ShredsOfSanity.java @@ -15,7 +15,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; -import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; /** * @@ -36,8 +36,8 @@ public final class ShredsOfSanity extends CardImpl { // Return up to one target instant card and up to one target sorcery card from your graveyard to your hand, then discard a card. Exile Shreds of Sanity. this.getSpellAbility().addEffect(new ShredsOfSanityEffect()); - this.getSpellAbility().addTarget(new TargetCard(0, 1, Zone.GRAVEYARD, filterInstant)); - this.getSpellAbility().addTarget(new TargetCard(0, 1, Zone.GRAVEYARD, filterSorcery)); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, filterInstant)); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, filterSorcery)); this.getSpellAbility().addEffect(new ExileSpellEffect()); } diff --git a/Mage.Sets/src/mage/cards/s/ShriekingDrake.java b/Mage.Sets/src/mage/cards/s/ShriekingDrake.java index e10d621b561..d67495df19d 100644 --- a/Mage.Sets/src/mage/cards/s/ShriekingDrake.java +++ b/Mage.Sets/src/mage/cards/s/ShriekingDrake.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -28,7 +27,7 @@ public final class ShriekingDrake extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Shrieking Drake enters the battlefield, return a creature you control to its owner's hand. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE))); } private ShriekingDrake(final ShriekingDrake card) { diff --git a/Mage.Sets/src/mage/cards/s/ShroudedLore.java b/Mage.Sets/src/mage/cards/s/ShroudedLore.java index 94f6795f9c2..af26890cb96 100644 --- a/Mage.Sets/src/mage/cards/s/ShroudedLore.java +++ b/Mage.Sets/src/mage/cards/s/ShroudedLore.java @@ -61,7 +61,7 @@ class ShroudedLoreEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (you != null && opponent != null) { FilterCard filter = new FilterCard(); filter.add(new OwnerIdPredicate(you.getId())); diff --git a/Mage.Sets/src/mage/cards/s/Shuriken.java b/Mage.Sets/src/mage/cards/s/Shuriken.java index 10d99277dfd..9f53df16c30 100644 --- a/Mage.Sets/src/mage/cards/s/Shuriken.java +++ b/Mage.Sets/src/mage/cards/s/Shuriken.java @@ -80,7 +80,7 @@ class ShurikenEffect extends OneShotEffect { Permanent equipment = (Permanent) object; targetedPermanent.damage(2, equipment.getId(), source, game); Permanent attached = source.getSourcePermanentOrLKI(game); - if (attached != null && attached.hasSubtype(SubType.NINJA, game)) { + if (attached == null || attached.hasSubtype(SubType.NINJA, game)) { return true; } game.addEffect(new GainControlTargetEffect( diff --git a/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java b/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java new file mode 100644 index 00000000000..9f90ebb7947 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SierraNukasBiggestFan.java @@ -0,0 +1,64 @@ +package mage.cards.s; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.FoodToken; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class SierraNukasBiggestFan extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.QUEST); + + public SierraNukasBiggestFan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // The Nuka-Cola Challenge -- Whenever one or more creatures you control deal combat damage to a player, + // put a quest counter on Sierra, Nuka's Biggest Fan and create a Food token. + Ability ability = new DealCombatDamageControlledTriggeredAbility(new AddCountersSourceEffect(CounterType.QUEST.createInstance())); + ability.addEffect(new CreateTokenEffect(new FoodToken()).concatBy("and")); + this.addAbility(ability.withFlavorWord("The Nuka-Cola Challenge")); + + // Whenever you sacrifice a Food, target creature you control gets +X/+X until end of turn, + // where X is the number of quest counters on Sierra. + Ability ability2 = new SacrificePermanentTriggeredAbility( + new BoostTargetEffect(xValue, xValue) + .setText("target creature you control gets +X/+X until end of turn, where X is the number of quest counters on {this}"), + StaticFilters.FILTER_CONTROLLED_FOOD); + ability2.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability2); + } + + private SierraNukasBiggestFan(final SierraNukasBiggestFan card) { + super(card); + } + + @Override + public SierraNukasBiggestFan copy() { + return new SierraNukasBiggestFan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigilOfTheNayanGods.java b/Mage.Sets/src/mage/cards/s/SigilOfTheNayanGods.java index 40b2c52af97..1f94b4192fa 100644 --- a/Mage.Sets/src/mage/cards/s/SigilOfTheNayanGods.java +++ b/Mage.Sets/src/mage/cards/s/SigilOfTheNayanGods.java @@ -16,7 +16,7 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -26,8 +26,6 @@ import mage.target.common.TargetCreaturePermanent; */ public final class SigilOfTheNayanGods extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - public SigilOfTheNayanGods(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{G}{W}"); this.subtype.add(SubType.AURA); @@ -39,7 +37,7 @@ public final class SigilOfTheNayanGods extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets +1/+1 for each creature you control. - PermanentsOnBattlefieldCount amount = new PermanentsOnBattlefieldCount(filter, 1); + PermanentsOnBattlefieldCount amount = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE, 1); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(amount, amount, Duration.WhileOnBattlefield))); // Cycling {G/W} diff --git a/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java b/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java new file mode 100644 index 00000000000..31e6232a1a0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAttachedEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.ShroudAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SilverShroudCostume extends CardImpl { + + public SilverShroudCostume(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Silver Shroud Costume enters the battlefield, attach it to target creature you control. That creature gains shroud until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new AttachEffect( + Outcome.BoostCreature, "attach it to target creature you control" + )); + ability.addEffect(new GainAbilityTargetEffect( + ShroudAbility.getInstance(), Duration.EndOfTurn + ).setText("That creature gains shroud until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Equipped creature can't be blocked. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT))); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private SilverShroudCostume(final SilverShroudCostume card) { + super(card); + } + + @Override + public SilverShroudCostume copy() { + return new SilverShroudCostume(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java index db9dcb4b95f..043b77c745a 100644 --- a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java +++ b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java @@ -6,7 +6,6 @@ import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.costs.common.ExileSourceFromGraveCost; import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfCostPaid; @@ -102,10 +101,10 @@ class SinisterConciergeEffect extends OneShotEffect { // Exile, put time counters, and give suspend for target Effect exileTarget = new ExileTargetEffect(); - exileTarget.setTargetPointer(this.getTargetPointer()); + exileTarget.setTargetPointer(this.getTargetPointer().copy()); if (exileTarget.apply(game, source)) { Effect addCountersTargetEffect = new AddCountersTargetEffect(CounterType.TIME.createInstance(3)); - addCountersTargetEffect.setTargetPointer(this.getTargetPointer()); + addCountersTargetEffect.setTargetPointer(this.getTargetPointer().copy()); boolean targetCardShouldGetSuspend = addCountersTargetEffect.apply(game, source); if (targetCardShouldGetSuspend && !targetCreature.getAbilities(game).containsClass(SuspendAbility.class)) { @@ -123,4 +122,4 @@ class SinisterConciergeEffect extends OneShotEffect { public SinisterConciergeEffect copy() { return new SinisterConciergeEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SkeletalVampire.java b/Mage.Sets/src/mage/cards/s/SkeletalVampire.java index 10a0b7fd00e..32986991c66 100644 --- a/Mage.Sets/src/mage/cards/s/SkeletalVampire.java +++ b/Mage.Sets/src/mage/cards/s/SkeletalVampire.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -16,9 +15,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.BatToken; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -26,11 +24,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class SkeletalVampire extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Bat"); - - static { - filter.add(SubType.BAT.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.BAT, "a Bat"); public SkeletalVampire(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}{B}"); diff --git a/Mage.Sets/src/mage/cards/s/SkirkFireMarshal.java b/Mage.Sets/src/mage/cards/s/SkirkFireMarshal.java index 1b79152605a..ccaf09a9c6d 100644 --- a/Mage.Sets/src/mage/cards/s/SkirkFireMarshal.java +++ b/Mage.Sets/src/mage/cards/s/SkirkFireMarshal.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -14,9 +13,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -24,7 +23,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class SkirkFireMarshal extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Goblins you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Goblins you control"); static { filter.add(TappedPredicate.UNTAPPED); @@ -44,7 +43,7 @@ public final class SkirkFireMarshal extends CardImpl { // Tap five untapped Goblins you control: Skirk Fire Marshal deals 10 damage to each creature and each player. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageEverythingEffect(10), - new TapTargetCost(new TargetControlledCreaturePermanent(5,5, filter, true))); + new TapTargetCost(new TargetControlledPermanent(5,5, filter, true))); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SkirkProspector.java b/Mage.Sets/src/mage/cards/s/SkirkProspector.java index b4b2a3f0405..f1fa1e9bae7 100644 --- a/Mage.Sets/src/mage/cards/s/SkirkProspector.java +++ b/Mage.Sets/src/mage/cards/s/SkirkProspector.java @@ -12,8 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -21,11 +20,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class SkirkProspector extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GOBLIN, "a Goblin"); public SkirkProspector(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{R}"); diff --git a/Mage.Sets/src/mage/cards/s/SkirsdagFlayer.java b/Mage.Sets/src/mage/cards/s/SkirsdagFlayer.java index 599c7b5bb76..3002830eccb 100644 --- a/Mage.Sets/src/mage/cards/s/SkirsdagFlayer.java +++ b/Mage.Sets/src/mage/cards/s/SkirsdagFlayer.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -14,8 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -24,11 +22,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class SkirsdagFlayer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Human"); - - static { - filter.add(SubType.HUMAN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Human"); public SkirsdagFlayer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); diff --git a/Mage.Sets/src/mage/cards/s/Skred.java b/Mage.Sets/src/mage/cards/s/Skred.java index 732a81b43ef..f96a82b528d 100644 --- a/Mage.Sets/src/mage/cards/s/Skred.java +++ b/Mage.Sets/src/mage/cards/s/Skred.java @@ -62,7 +62,7 @@ class SkredDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int amount = game.getBattlefield().count(filter, source.getControllerId(), source, game); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if(amount > 0) { if (permanent != null) { permanent.damage(amount, source.getSourceId(), source, game, false, true); diff --git a/Mage.Sets/src/mage/cards/s/Skullscorch.java b/Mage.Sets/src/mage/cards/s/Skullscorch.java index 4b1993ffe33..87759833f85 100644 --- a/Mage.Sets/src/mage/cards/s/Skullscorch.java +++ b/Mage.Sets/src/mage/cards/s/Skullscorch.java @@ -67,7 +67,7 @@ class SkullscorchDiscardEffect extends OneShotEffect { } if (spell != null) { boolean discardCards = true; - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { if (player.chooseUse(Outcome.Detriment, "Have " + spell.getLogName() + " deal 4 damage to you?", source, game)) { discardCards = false; diff --git a/Mage.Sets/src/mage/cards/s/SlaughterGames.java b/Mage.Sets/src/mage/cards/s/SlaughterGames.java index dd56bef94e7..b5b5fe6b326 100644 --- a/Mage.Sets/src/mage/cards/s/SlaughterGames.java +++ b/Mage.Sets/src/mage/cards/s/SlaughterGames.java @@ -55,7 +55,7 @@ class SlaughterGamesEffect extends SearchTargetGraveyardHandLibraryForCardNameAn if (cardName == null) { return false; } - return super.applySearchAndExile(game, source, cardName, targetPointer.getFirst(game, source)); + return super.applySearchAndExile(game, source, cardName, getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/s/Smallpox.java b/Mage.Sets/src/mage/cards/s/Smallpox.java index 79a4ea0ed4c..3d2d9b495b4 100644 --- a/Mage.Sets/src/mage/cards/s/Smallpox.java +++ b/Mage.Sets/src/mage/cards/s/Smallpox.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -9,9 +8,7 @@ import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledLandPermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; /** * @@ -19,22 +16,18 @@ import mage.filter.common.FilterControlledPermanent; */ public final class Smallpox extends CardImpl { - private static final FilterControlledPermanent filterCreature = new FilterControlledCreaturePermanent(); - private static final FilterControlledPermanent filterLand = new FilterControlledLandPermanent(); - public Smallpox(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{B}{B}"); - // Each player loses 1 life, discards a card, sacrifices a creature, then sacrifices a land. this.getSpellAbility().addEffect(new LoseLifeAllPlayersEffect(1)); Effect effect = new DiscardEachPlayerEffect(); effect.setText(", discards a card"); this.getSpellAbility().addEffect(effect); - effect = new SacrificeAllEffect(1, filterCreature); + effect = new SacrificeAllEffect(1, StaticFilters.FILTER_CONTROLLED_CREATURE); effect.setText(", sacrifices a creature"); this.getSpellAbility().addEffect(effect); - effect = new SacrificeAllEffect(1, filterLand); + effect = new SacrificeAllEffect(1, StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND); effect.setText(", then sacrifices a land"); this.getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/s/Smoke.java b/Mage.Sets/src/mage/cards/s/Smoke.java index 9b948afb07a..0a2784f7d8e 100644 --- a/Mage.Sets/src/mage/cards/s/Smoke.java +++ b/Mage.Sets/src/mage/cards/s/Smoke.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -10,8 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -25,7 +23,6 @@ public final class Smoke extends CardImpl { public Smoke(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{R}{R}"); - // Players can't untap more than one creature during their untap steps. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SmokeEffect())); } @@ -42,10 +39,8 @@ public final class Smoke extends CardImpl { class SmokeEffect extends RestrictionUntapNotMoreThanEffect { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent(); - - public SmokeEffect() { - super(Duration.WhileOnBattlefield, 1, filter); + SmokeEffect() { + super(Duration.WhileOnBattlefield, 1, StaticFilters.FILTER_CONTROLLED_CREATURE); staticText = "Players can't untap more than one creature during their untap steps"; } diff --git a/Mage.Sets/src/mage/cards/s/SmotheringTithe.java b/Mage.Sets/src/mage/cards/s/SmotheringTithe.java index 626b9b169a7..a23b5452b25 100644 --- a/Mage.Sets/src/mage/cards/s/SmotheringTithe.java +++ b/Mage.Sets/src/mage/cards/s/SmotheringTithe.java @@ -57,7 +57,7 @@ class SmotheringTitheEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SnapcasterMage.java b/Mage.Sets/src/mage/cards/s/SnapcasterMage.java index 28e1b91d7d3..ea247bb0b21 100644 --- a/Mage.Sets/src/mage/cards/s/SnapcasterMage.java +++ b/Mage.Sets/src/mage/cards/s/SnapcasterMage.java @@ -75,7 +75,7 @@ class SnapcasterMageEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); diff --git a/Mage.Sets/src/mage/cards/s/SnowCoveredWastes.java b/Mage.Sets/src/mage/cards/s/SnowCoveredWastes.java new file mode 100644 index 00000000000..11f4659004a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SnowCoveredWastes.java @@ -0,0 +1,33 @@ +package mage.cards.s; + +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SnowCoveredWastes extends CardImpl { + + public SnowCoveredWastes(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + this.supertype.add(SuperType.BASIC); + this.supertype.add(SuperType.SNOW); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + } + + private SnowCoveredWastes(final SnowCoveredWastes card) { + super(card); + } + + @Override + public SnowCoveredWastes copy() { + return new SnowCoveredWastes(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SnowHound.java b/Mage.Sets/src/mage/cards/s/SnowHound.java index 236c3a0f563..ba72ef47f7d 100644 --- a/Mage.Sets/src/mage/cards/s/SnowHound.java +++ b/Mage.Sets/src/mage/cards/s/SnowHound.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -41,11 +40,11 @@ public final class SnowHound extends CardImpl { // {1}, {tap}: Return Snow Hound and target green or blue creature you control to their owner's hand. Effect effect = new ReturnToHandSourceEffect(true); - effect.setText("Return Snow Hound"); + effect.setText("Return {this}"); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{1}")); ability.addCost(new TapSourceCost()); effect = new ReturnToHandTargetEffect(); - effect.setText("and target green or blue creature you control to their owners' hands"); + effect.setText("and target green or blue creature you control to their owner's hand"); ability.addTarget(new TargetControlledCreaturePermanent(filter)); ability.addEffect(effect); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SootImp.java b/Mage.Sets/src/mage/cards/s/SootImp.java index 9f9ebb1cfe2..f396666be75 100644 --- a/Mage.Sets/src/mage/cards/s/SootImp.java +++ b/Mage.Sets/src/mage/cards/s/SootImp.java @@ -75,7 +75,7 @@ class SootImpEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player caster = game.getPlayer(targetPointer.getFirst(game, source)); + Player caster = game.getPlayer(getTargetPointer().getFirst(game, source)); if (caster != null) { caster.loseLife(1, game, source, false); return true; diff --git a/Mage.Sets/src/mage/cards/s/SophiaDoggedDetective.java b/Mage.Sets/src/mage/cards/s/SophiaDoggedDetective.java new file mode 100644 index 00000000000..bacaa547a29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SophiaDoggedDetective.java @@ -0,0 +1,69 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.FoodToken; +import mage.game.permanent.token.TinyToken; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class SophiaDoggedDetective extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DOG, "Dog you control"); + private static final FilterArtifactPermanent filter2 = new FilterArtifactPermanent("artifact token"); + + static { + filter2.add(TokenPredicate.TRUE); + } + + public SophiaDoggedDetective(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}{U}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN, SubType.DETECTIVE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Sophia, Dogged Detective enters the battlefield, create Tiny, a legendary 2/2 green Dog Detective creature token with trample. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TinyToken()))); + + // {1}, Sacrifice an artifact token: Put a +1/+1 counter on each Dog you control. + Ability ability = new SimpleActivatedAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new GenericManaCost(1)); + ability.addCost(new SacrificeTargetCost(filter2)); + this.addAbility(ability); + + // Whenever a Dog you control deals combat damage to a player, create a Food token, then investigate. + ability = new DealsDamageToAPlayerAllTriggeredAbility(new CreateTokenEffect(new FoodToken()), filter, false, SetTargetPointer.NONE, true); + ability.addEffect(new InvestigateEffect(false).concatBy(", then")); + this.addAbility(ability); + } + + private SophiaDoggedDetective(final SophiaDoggedDetective card) { + super(card); + } + + @Override + public SophiaDoggedDetective copy() { + return new SophiaDoggedDetective(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SorinMarkov.java b/Mage.Sets/src/mage/cards/s/SorinMarkov.java index 5e63fa05759..a9e31aeedea 100644 --- a/Mage.Sets/src/mage/cards/s/SorinMarkov.java +++ b/Mage.Sets/src/mage/cards/s/SorinMarkov.java @@ -73,7 +73,7 @@ class SorinMarkovEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.setLife(10, game, source); return true; diff --git a/Mage.Sets/src/mage/cards/s/SoulEcho.java b/Mage.Sets/src/mage/cards/s/SoulEcho.java index 463eb9772f1..059a283c69c 100644 --- a/Mage.Sets/src/mage/cards/s/SoulEcho.java +++ b/Mage.Sets/src/mage/cards/s/SoulEcho.java @@ -80,7 +80,7 @@ class SoulEchoOpponentsChoiceEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && opponent != null && permanent != null) { if (opponent.chooseUse(outcome, "Have all damage dealt to " + controller.getLogName() + " be decremented from echo counters on " + permanent.getLogName() + " until " + controller.getLogName() + "'s next upkeep instead?", source, game)) { game.informPlayers("Until " + controller.getLogName() + "'s next upkeep, for each 1 damage that would be dealt to " + controller.getLogName() + ", an echo counter from " + permanent.getLogName() + " is removed instead"); diff --git a/Mage.Sets/src/mage/cards/s/SoulRend.java b/Mage.Sets/src/mage/cards/s/SoulRend.java index bbb16613dcc..fc785453b32 100644 --- a/Mage.Sets/src/mage/cards/s/SoulRend.java +++ b/Mage.Sets/src/mage/cards/s/SoulRend.java @@ -55,7 +55,7 @@ class SoulRendEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && permanent.getColor(game).isWhite()) { permanent.destroy(source, game, true); } diff --git a/Mage.Sets/src/mage/cards/s/SoulSeizer.java b/Mage.Sets/src/mage/cards/s/SoulSeizer.java index 6c471f4b244..43db8c735ff 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSeizer.java +++ b/Mage.Sets/src/mage/cards/s/SoulSeizer.java @@ -113,7 +113,7 @@ class SoulSeizerEffect extends OneShotEffect { if (permanent == null || !permanent.transform(source, game)) { return false; } - Permanent attachTo = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent attachTo = game.getPermanent(getTargetPointer().getFirst(game, source)); return attachTo != null && attachTo.addAttachment(source.getSourceId(), source, game); } diff --git a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java index a23b04606cd..588df99b9b3 100644 --- a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java @@ -15,6 +15,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.events.GameEvent; @@ -29,7 +30,7 @@ import java.util.UUID; */ public final class SoulfireGrandMaster extends CardImpl { - private static final FilterCard filter = new FilterCard("instant and sorcery spells you control"); + private static final FilterNonlandCard filter = new FilterNonlandCard("instant and sorcery spells you control"); static { filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate())); diff --git a/Mage.Sets/src/mage/cards/s/SpawnOfThraxes.java b/Mage.Sets/src/mage/cards/s/SpawnOfThraxes.java index 97e7f99950a..8951c225dea 100644 --- a/Mage.Sets/src/mage/cards/s/SpawnOfThraxes.java +++ b/Mage.Sets/src/mage/cards/s/SpawnOfThraxes.java @@ -37,7 +37,8 @@ public final class SpawnOfThraxes extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Spawn of Thraxes enters the battlefield, it deals damage to any target equal to the number of Mountains you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter))); + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter)) + .setText("it deals damage to any target equal to the number of Mountains you control")); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SpawnbinderMage.java b/Mage.Sets/src/mage/cards/s/SpawnbinderMage.java index ab4528c0115..6cb7d40d02f 100644 --- a/Mage.Sets/src/mage/cards/s/SpawnbinderMage.java +++ b/Mage.Sets/src/mage/cards/s/SpawnbinderMage.java @@ -1,37 +1,23 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author fireshoes */ public final class SpawnbinderMage extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public SpawnbinderMage(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}"); this.subtype.add(SubType.HUMAN); @@ -41,10 +27,8 @@ public final class SpawnbinderMage extends CardImpl { this.toughness = new MageInt(4); // Cohort — {T}, Tap an untapped Ally you control: Tap target creature. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TapTargetEffect(), new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); + Ability ability = new CohortAbility(new TapTargetEffect()); ability.addTarget(new TargetCreaturePermanent()); - ability.setAbilityWord(AbilityWord.COHORT); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SpeciesGorger.java b/Mage.Sets/src/mage/cards/s/SpeciesGorger.java index 0bcb9d96c9c..cd5008b3072 100644 --- a/Mage.Sets/src/mage/cards/s/SpeciesGorger.java +++ b/Mage.Sets/src/mage/cards/s/SpeciesGorger.java @@ -8,7 +8,7 @@ import mage.abilities.effects.common.ReturnToHandChosenControlledPermanentEffect import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -25,7 +25,7 @@ public final class SpeciesGorger extends CardImpl { this.toughness = new MageInt(6); // At the beginning of your upkeep, return a creature you control to its owner's hand. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()), TargetController.YOU, false)); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE), TargetController.YOU, false)); } @@ -38,4 +38,3 @@ public final class SpeciesGorger extends CardImpl { return new SpeciesGorger(this); } } - diff --git a/Mage.Sets/src/mage/cards/s/SpellRupture.java b/Mage.Sets/src/mage/cards/s/SpellRupture.java index fd99ec6262c..3077c3f0b9e 100644 --- a/Mage.Sets/src/mage/cards/s/SpellRupture.java +++ b/Mage.Sets/src/mage/cards/s/SpellRupture.java @@ -64,7 +64,7 @@ class SpellRuptureCounterUnlessPaysEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/s/SpellSwindle.java b/Mage.Sets/src/mage/cards/s/SpellSwindle.java index 139ca69419a..6d74e298a4e 100644 --- a/Mage.Sets/src/mage/cards/s/SpellSwindle.java +++ b/Mage.Sets/src/mage/cards/s/SpellSwindle.java @@ -54,7 +54,7 @@ class SpellSwindleEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject stackObject = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackObject != null) { game.getStack().counter(source.getFirstTarget(), source, game); return new TreasureToken().putOntoBattlefield(stackObject.getManaValue(), game, source); diff --git a/Mage.Sets/src/mage/cards/s/SpellSyphon.java b/Mage.Sets/src/mage/cards/s/SpellSyphon.java index 1225cbfb1e6..ea87b008337 100644 --- a/Mage.Sets/src/mage/cards/s/SpellSyphon.java +++ b/Mage.Sets/src/mage/cards/s/SpellSyphon.java @@ -70,7 +70,7 @@ class SpellSyphonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell != null) { Player player = game.getPlayer(spell.getControllerId()); Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/s/Spelljack.java b/Mage.Sets/src/mage/cards/s/Spelljack.java index 4f4f4d44bc3..4fb3ec7e652 100644 --- a/Mage.Sets/src/mage/cards/s/Spelljack.java +++ b/Mage.Sets/src/mage/cards/s/Spelljack.java @@ -61,7 +61,7 @@ class SpelljackEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - UUID targetId = targetPointer.getFirst(game, source); + UUID targetId = getTargetPointer().getFirst(game, source); StackObject stackObject = game.getStack().getStackObject(targetId); if (stackObject != null && game.getStack().counter(targetId, source, game, PutCards.EXILED)) { Card card = ((Spell) stackObject).getCard(); diff --git a/Mage.Sets/src/mage/cards/s/Spellshift.java b/Mage.Sets/src/mage/cards/s/Spellshift.java index 0f92197a256..b12b4f15306 100644 --- a/Mage.Sets/src/mage/cards/s/Spellshift.java +++ b/Mage.Sets/src/mage/cards/s/Spellshift.java @@ -65,7 +65,7 @@ class SpellshiftEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player spellController = game.getPlayer(((Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK)).getControllerId()); + Player spellController = game.getPlayer(((Spell) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK)).getControllerId()); if (spellController != null) { Cards cardsToReveal = new CardsImpl(); Card toCast = null; diff --git a/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java b/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java index 9139d970c0f..cd61086ebd6 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java +++ b/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java @@ -71,7 +71,7 @@ class SphinxAmbassadorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (controller != null && targetPlayer != null && sourcePermanent != null) { TargetCardInLibrary target = new TargetCardInLibrary(); diff --git a/Mage.Sets/src/mage/cards/s/SpinalEmbrace.java b/Mage.Sets/src/mage/cards/s/SpinalEmbrace.java index fc056bf55b7..016d3b5a2a4 100644 --- a/Mage.Sets/src/mage/cards/s/SpinalEmbrace.java +++ b/Mage.Sets/src/mage/cards/s/SpinalEmbrace.java @@ -101,7 +101,7 @@ class SpinalEmbraceSacrificeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { permanent.sacrifice(source, game); diff --git a/Mage.Sets/src/mage/cards/s/Spitemare.java b/Mage.Sets/src/mage/cards/s/Spitemare.java index ed9857050b1..f3e370bd656 100644 --- a/Mage.Sets/src/mage/cards/s/Spitemare.java +++ b/Mage.Sets/src/mage/cards/s/Spitemare.java @@ -2,19 +2,13 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -32,7 +26,7 @@ public final class Spitemare extends CardImpl { this.toughness = new MageInt(3); // Whenever Spitemare is dealt damage, it deals that much damage to any target. - Ability ability = new SpitemareTriggeredAbility(); + Ability ability = new DealtDamageToSourceTriggeredAbility(new DamageTargetEffect(SavedDamageValue.MUCH), false); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); @@ -47,66 +41,3 @@ public final class Spitemare extends CardImpl { return new Spitemare(this); } } - -class SpitemareTriggeredAbility extends TriggeredAbilityImpl { - - public SpitemareTriggeredAbility() { - super(Zone.BATTLEFIELD, new SpitemareEffect()); - setTriggerPhrase("Whenever {this} is dealt damage, "); - } - - private SpitemareTriggeredAbility(final SpitemareTriggeredAbility effect) { - super(effect); - } - - @Override - public SpitemareTriggeredAbility copy() { - return new SpitemareTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId)) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } -} - -class SpitemareEffect extends OneShotEffect { - - SpitemareEffect() { - super(Outcome.Damage); - staticText = "it deals that much damage to any target"; - } - - private SpitemareEffect(final SpitemareEffect effect) { - super(effect); - } - - @Override - public SpitemareEffect copy() { - return new SpitemareEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - player.damage((Integer) this.getValue("damageAmount"), source.getSourceId(), source, game); - return true; - } - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - permanent.damage((Integer) this.getValue("damageAmount"), source.getSourceId(), source, game, false, true); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SplinteringWind.java b/Mage.Sets/src/mage/cards/s/SplinteringWind.java index b227718670b..e9bc5fccd87 100644 --- a/Mage.Sets/src/mage/cards/s/SplinteringWind.java +++ b/Mage.Sets/src/mage/cards/s/SplinteringWind.java @@ -15,7 +15,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -86,11 +86,11 @@ class SplinteringWindCreateTokenEffect extends OneShotEffect { class SplinteringWindDelayedTriggeredAbility extends DelayedTriggeredAbility { - private UUID tokenId; + private final UUID tokenId; SplinteringWindDelayedTriggeredAbility(UUID tokenId) { super(new DamageControllerEffect(1), Duration.OneUse); - this.addEffect(new DamageAllEffect(1, new FilterControlledCreaturePermanent())); + this.addEffect(new DamageAllEffect(1, StaticFilters.FILTER_CONTROLLED_CREATURE)); this.tokenId = tokenId; } diff --git a/Mage.Sets/src/mage/cards/s/SplitTheParty.java b/Mage.Sets/src/mage/cards/s/SplitTheParty.java index 37c8e3403ae..21ab34362f1 100644 --- a/Mage.Sets/src/mage/cards/s/SplitTheParty.java +++ b/Mage.Sets/src/mage/cards/s/SplitTheParty.java @@ -63,7 +63,7 @@ class SplitThePartyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || targetPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SpoilsOfEvil.java b/Mage.Sets/src/mage/cards/s/SpoilsOfEvil.java index 2b218909718..c314775d235 100644 --- a/Mage.Sets/src/mage/cards/s/SpoilsOfEvil.java +++ b/Mage.Sets/src/mage/cards/s/SpoilsOfEvil.java @@ -62,7 +62,7 @@ public final class SpoilsOfEvil extends CardImpl { @Override public boolean apply(Game game, Ability source) { - Player targetOpponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if(targetOpponent != null && controller != null) { diff --git a/Mage.Sets/src/mage/cards/s/SpringjackPasture.java b/Mage.Sets/src/mage/cards/s/SpringjackPasture.java index e4c9280aca8..27f896bb520 100644 --- a/Mage.Sets/src/mage/cards/s/SpringjackPasture.java +++ b/Mage.Sets/src/mage/cards/s/SpringjackPasture.java @@ -16,7 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.GoatToken; import java.util.UUID; @@ -26,11 +26,7 @@ import java.util.UUID; */ public final class SpringjackPasture extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Goats"); - - static { - filter.add(SubType.GOAT.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GOAT, "Goats"); public SpringjackPasture(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); diff --git a/Mage.Sets/src/mage/cards/s/StainTheMind.java b/Mage.Sets/src/mage/cards/s/StainTheMind.java index bfa8de41fcc..2b7ab1aec5d 100644 --- a/Mage.Sets/src/mage/cards/s/StainTheMind.java +++ b/Mage.Sets/src/mage/cards/s/StainTheMind.java @@ -54,7 +54,7 @@ class StainTheMindEffect extends SearchTargetGraveyardHandLibraryForCardNameAndE if (cardName == null) { return false; } - return super.applySearchAndExile(game, source, cardName, targetPointer.getFirst(game, source)); + return super.applySearchAndExile(game, source, cardName, getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/s/StampedeSurfer.java b/Mage.Sets/src/mage/cards/s/StampedeSurfer.java new file mode 100644 index 00000000000..057ba8b32e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StampedeSurfer.java @@ -0,0 +1,70 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.token.Boar2Token; +import mage.game.permanent.token.Token; + +import java.util.UUID; + +public final class StampedeSurfer extends CardImpl { + + public StampedeSurfer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R/G}{R/G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new mage.MageInt(4); + this.toughness = new mage.MageInt(4); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Stampede Surfer attacks, for each opponent, you create a 2/2 green Boar creature token that's tapped and attacking that opponent. + this.addAbility(new AttacksTriggeredAbility(new StampedeSurferEffect())); + } + + private StampedeSurfer(final StampedeSurfer card) { + super(card); + } + + @Override + public StampedeSurfer copy() { + return new StampedeSurfer(this); + } +} + +class StampedeSurferEffect extends OneShotEffect { + + private static final Token token = new Boar2Token(); + + StampedeSurferEffect() { + super(Outcome.Benefit); + staticText = "for each opponent, you create a 2/2 green Boar creature token that's tapped and attacking that opponent"; + } + + private StampedeSurferEffect(final StampedeSurferEffect effect) { + super(effect); + } + + @Override + public StampedeSurferEffect copy() { + return new StampedeSurferEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true, playerId); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StampedingHorncrest.java b/Mage.Sets/src/mage/cards/s/StampedingHorncrest.java index 158c169ac96..80b0a0c3e62 100644 --- a/Mage.Sets/src/mage/cards/s/StampedingHorncrest.java +++ b/Mage.Sets/src/mage/cards/s/StampedingHorncrest.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -23,7 +23,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; */ public final class StampedingHorncrest extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Dinosaur"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another Dinosaur"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/s/StensiaBanquet.java b/Mage.Sets/src/mage/cards/s/StensiaBanquet.java index 85de9d43797..4b59c00f0e3 100644 --- a/Mage.Sets/src/mage/cards/s/StensiaBanquet.java +++ b/Mage.Sets/src/mage/cards/s/StensiaBanquet.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetOpponentOrPlaneswalker; /** @@ -19,11 +18,7 @@ import mage.target.common.TargetOpponentOrPlaneswalker; */ public final class StensiaBanquet extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Vampires you control"); - - static { - filter.add(SubType.VAMPIRE.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.VAMPIRE, "Vampires you control"); public StensiaBanquet(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); diff --git a/Mage.Sets/src/mage/cards/s/StigmaLasher.java b/Mage.Sets/src/mage/cards/s/StigmaLasher.java index 674fe5c7aea..847b752d5c6 100644 --- a/Mage.Sets/src/mage/cards/s/StigmaLasher.java +++ b/Mage.Sets/src/mage/cards/s/StigmaLasher.java @@ -69,7 +69,7 @@ class StigmaLasherEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(this.targetPointer.getFirst(game, source)); + Player player = game.getPlayer(this.getTargetPointer().getFirst(game, source)); if (player != null) { player.setCanGainLife(false); } diff --git a/Mage.Sets/src/mage/cards/s/StinkdrinkerBandit.java b/Mage.Sets/src/mage/cards/s/StinkdrinkerBandit.java index 1a213fca9f0..62e8b552c20 100644 --- a/Mage.Sets/src/mage/cards/s/StinkdrinkerBandit.java +++ b/Mage.Sets/src/mage/cards/s/StinkdrinkerBandit.java @@ -10,7 +10,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; @@ -24,12 +23,6 @@ import java.util.UUID; */ public final class StinkdrinkerBandit extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Rogue"); - - static { - filter.add(SubType.ROGUE.getPredicate()); - } - public StinkdrinkerBandit(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.GOBLIN); diff --git a/Mage.Sets/src/mage/cards/s/StoicAngel.java b/Mage.Sets/src/mage/cards/s/StoicAngel.java index 6d9d8df0a5a..8f07538efa1 100644 --- a/Mage.Sets/src/mage/cards/s/StoicAngel.java +++ b/Mage.Sets/src/mage/cards/s/StoicAngel.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -14,8 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -52,10 +50,8 @@ public final class StoicAngel extends CardImpl { class StoicAngelEffect extends RestrictionUntapNotMoreThanEffect { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent(); - - public StoicAngelEffect() { - super(Duration.WhileOnBattlefield, 1, filter); + StoicAngelEffect() { + super(Duration.WhileOnBattlefield, 1, StaticFilters.FILTER_CONTROLLED_CREATURE); staticText = "Players can't untap more than one creature during their untap steps"; } diff --git a/Mage.Sets/src/mage/cards/s/StolenGoods.java b/Mage.Sets/src/mage/cards/s/StolenGoods.java index 4c6b0590dea..404f858e5db 100644 --- a/Mage.Sets/src/mage/cards/s/StolenGoods.java +++ b/Mage.Sets/src/mage/cards/s/StolenGoods.java @@ -58,7 +58,7 @@ class StolenGoodsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/Stonecloaker.java b/Mage.Sets/src/mage/cards/s/Stonecloaker.java index e6adafafbb6..97f46386645 100644 --- a/Mage.Sets/src/mage/cards/s/Stonecloaker.java +++ b/Mage.Sets/src/mage/cards/s/Stonecloaker.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -13,7 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInGraveyard; /** @@ -34,7 +33,7 @@ public final class Stonecloaker extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Stonecloaker enters the battlefield, return a creature you control to its owner's hand. - Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE), false); this.addAbility(ability); // When Stonecloaker enters the battlefield, exile target card from a graveyard. diff --git a/Mage.Sets/src/mage/cards/s/StoneforgeAcolyte.java b/Mage.Sets/src/mage/cards/s/StoneforgeAcolyte.java index 4077566f2b6..bea6e937265 100644 --- a/Mage.Sets/src/mage/cards/s/StoneforgeAcolyte.java +++ b/Mage.Sets/src/mage/cards/s/StoneforgeAcolyte.java @@ -1,23 +1,16 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.PutCards; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @@ -25,12 +18,9 @@ import mage.target.common.TargetControlledPermanent; */ public final class StoneforgeAcolyte extends CardImpl { - private static final FilterControlledPermanent filterAlly = new FilterControlledPermanent("an untapped Ally you control"); private static final FilterCard filterEquipment = new FilterCard("an Equipment card"); static { - filterAlly.add(SubType.ALLY.getPredicate()); - filterAlly.add(TappedPredicate.UNTAPPED); filterEquipment.add(SubType.EQUIPMENT.getPredicate()); } @@ -44,12 +34,9 @@ public final class StoneforgeAcolyte extends CardImpl { // Cohort — {T}, Tap an untapped Ally you control: Look at the top four cards of your library. // You may reveal an Equipment card from among them and put it into your hand. Put the rest on the bottom of your library in any order. - Ability ability = new SimpleActivatedAbility( - new LookLibraryAndPickControllerEffect(4, 1, filterEquipment, PutCards.HAND, PutCards.BOTTOM_ANY), - new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filterAlly))); - ability.setAbilityWord(AbilityWord.COHORT); - this.addAbility(ability); + this.addAbility(new CohortAbility(new LookLibraryAndPickControllerEffect( + 4, 1, filterEquipment, PutCards.HAND, PutCards.BOTTOM_ANY + ))); } private StoneforgeAcolyte(final StoneforgeAcolyte card) { diff --git a/Mage.Sets/src/mage/cards/s/StonewiseFortifier.java b/Mage.Sets/src/mage/cards/s/StonewiseFortifier.java index e301099cbf9..5d10eab61bb 100644 --- a/Mage.Sets/src/mage/cards/s/StonewiseFortifier.java +++ b/Mage.Sets/src/mage/cards/s/StonewiseFortifier.java @@ -88,7 +88,7 @@ class StonewiseFortifierPreventAllDamageToEffect extends PreventionEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (super.applies(event, source, game) && event.getTargetId().equals(source.getSourceId())) { - return event.getSourceId().equals(targetPointer.getFirst(game, source)); + return event.getSourceId().equals(getTargetPointer().getFirst(game, source)); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/StormOfSouls.java b/Mage.Sets/src/mage/cards/s/StormOfSouls.java index 33afb0bda2f..69f61487f80 100644 --- a/Mage.Sets/src/mage/cards/s/StormOfSouls.java +++ b/Mage.Sets/src/mage/cards/s/StormOfSouls.java @@ -100,7 +100,7 @@ class StormOfSoulsChangeCreatureEffect extends ContinuousEffectImpl { } // Each of them is a 1/1 Spirit with flying in addition to its other types - for (UUID cardID : targetPointer.getTargets(game, source)) { + for (UUID cardID : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(cardID); if (permanent == null) { continue; diff --git a/Mage.Sets/src/mage/cards/s/StormSculptor.java b/Mage.Sets/src/mage/cards/s/StormSculptor.java index 70a180b3249..66eb5be3b27 100644 --- a/Mage.Sets/src/mage/cards/s/StormSculptor.java +++ b/Mage.Sets/src/mage/cards/s/StormSculptor.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -10,7 +9,7 @@ import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -30,7 +29,7 @@ public final class StormSculptor extends CardImpl { this.addAbility(new CantBeBlockedSourceAbility()); // When Storm Sculptor enters the battlefield, return a creature you control to its owner's hand. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE))); } private StormSculptor(final StormSculptor card) { diff --git a/Mage.Sets/src/mage/cards/s/StormWorld.java b/Mage.Sets/src/mage/cards/s/StormWorld.java index 80a9205d92a..0ba6d88084d 100644 --- a/Mage.Sets/src/mage/cards/s/StormWorld.java +++ b/Mage.Sets/src/mage/cards/s/StormWorld.java @@ -54,7 +54,7 @@ class StormWorldEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int damage = 4 - player.getHand().size(); if (damage > 0) { diff --git a/Mage.Sets/src/mage/cards/s/StormtideLeviathan.java b/Mage.Sets/src/mage/cards/s/StormtideLeviathan.java index 5da70e371a4..79cd384b4d8 100644 --- a/Mage.Sets/src/mage/cards/s/StormtideLeviathan.java +++ b/Mage.Sets/src/mage/cards/s/StormtideLeviathan.java @@ -1,22 +1,19 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.combat.CantAttackAnyPlayerAllEffect; +import mage.abilities.effects.common.continuous.AddBasicLandTypeAllLandsEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.IslandwalkAbility; -import mage.abilities.mana.BlueManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.StaticFilters; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; import java.util.UUID; @@ -44,7 +41,8 @@ public final class StormtideLeviathan extends CardImpl { this.addAbility(new IslandwalkAbility()); // All lands are Islands in addition to their other types. - this.addAbility(new SimpleStaticAbility(new StormtideLeviathanEffect())); + this.addAbility(new SimpleStaticAbility(new AddBasicLandTypeAllLandsEffect(SubType.ISLAND) + .setText("all lands are Islands in addition to their other types"))); // Creatures without flying or islandwalk can't attack. this.addAbility(new SimpleStaticAbility(new CantAttackAnyPlayerAllEffect(Duration.WhileOnBattlefield, filter))); @@ -59,35 +57,4 @@ public final class StormtideLeviathan extends CardImpl { return new StormtideLeviathan(this); } - class StormtideLeviathanEffect extends ContinuousEffectImpl { - - private StormtideLeviathanEffect() { - super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral); - staticText = "All lands are Islands in addition to their other types"; - this.dependencyTypes.add(DependencyType.BecomeIsland); - } - - private StormtideLeviathanEffect(final StormtideLeviathanEffect effect) { - super(effect); - } - - @Override - public StormtideLeviathanEffect copy() { - return new StormtideLeviathanEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (Permanent land : game.getBattlefield().getActivePermanents( - StaticFilters.FILTER_LAND, source.getControllerId(), game - )) { - // land abilities are intrinsic, so add them here, not in layer 6 - land.addSubType(game, SubType.ISLAND); - if (!land.getAbilities(game).containsClass(BlueManaAbility.class)) { - land.addAbility(new BlueManaAbility(), source.getSourceId(), game); - } - } - return true; - } - } } diff --git a/Mage.Sets/src/mage/cards/s/StoryCircle.java b/Mage.Sets/src/mage/cards/s/StoryCircle.java index f0c1c7d4097..3156d8a9f75 100644 --- a/Mage.Sets/src/mage/cards/s/StoryCircle.java +++ b/Mage.Sets/src/mage/cards/s/StoryCircle.java @@ -54,9 +54,9 @@ class StoryCircleEffect extends PreventNextDamageFromChosenSourceToYouEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); FilterObject filter = targetSource.getFilter(); filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color"))); - super.init(source, game); } private StoryCircleEffect(final StoryCircleEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/Stranglehold.java b/Mage.Sets/src/mage/cards/s/Stranglehold.java index fb7992b0193..f0d42ce4821 100644 --- a/Mage.Sets/src/mage/cards/s/Stranglehold.java +++ b/Mage.Sets/src/mage/cards/s/Stranglehold.java @@ -1,12 +1,11 @@ - package mage.cards.s; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SkipExtraTurnsAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -28,10 +27,10 @@ public final class Stranglehold extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{R}"); // Your opponents can't search libraries. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new OpponentsCantSearchLibarariesEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new OpponentsCantSearchLibrariesEffect())); // If an opponent would begin an extra turn, that player skips that turn instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new StrangleholdSkipExtraTurnsEffect())); + this.addAbility(new SkipExtraTurnsAbility(true)); } private Stranglehold(final Stranglehold card) { @@ -44,20 +43,20 @@ public final class Stranglehold extends CardImpl { } } -class OpponentsCantSearchLibarariesEffect extends ContinuousRuleModifyingEffectImpl { +class OpponentsCantSearchLibrariesEffect extends ContinuousRuleModifyingEffectImpl { - OpponentsCantSearchLibarariesEffect() { + OpponentsCantSearchLibrariesEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit, true, false); staticText = "Your opponents can't search libraries"; } - private OpponentsCantSearchLibarariesEffect(final OpponentsCantSearchLibarariesEffect effect) { + private OpponentsCantSearchLibrariesEffect(final OpponentsCantSearchLibrariesEffect effect) { super(effect); } @Override - public OpponentsCantSearchLibarariesEffect copy() { - return new OpponentsCantSearchLibarariesEffect(this); + public OpponentsCantSearchLibrariesEffect copy() { + return new OpponentsCantSearchLibrariesEffect(this); } @Override @@ -80,42 +79,3 @@ class OpponentsCantSearchLibarariesEffect extends ContinuousRuleModifyingEffectI return controller != null && controller.hasOpponent(event.getPlayerId(), game); } } - -class StrangleholdSkipExtraTurnsEffect extends ReplacementEffectImpl { - - StrangleholdSkipExtraTurnsEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "If an opponent would begin an extra turn, that player skips that turn instead"; - } - - private StrangleholdSkipExtraTurnsEffect(final StrangleholdSkipExtraTurnsEffect effect) { - super(effect); - } - - @Override - public StrangleholdSkipExtraTurnsEffect copy() { - return new StrangleholdSkipExtraTurnsEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - MageObject sourceObject = game.getObject(source); - if (player != null && sourceObject != null) { - game.informPlayers(sourceObject.getLogName() + ": Extra turn of " + player.getLogName() + " skipped"); - } - return true; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.EXTRA_TURN; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - return controller != null && controller.hasOpponent(event.getPlayerId(), game); - } - -} diff --git a/Mage.Sets/src/mage/cards/s/StrengthOfThePack.java b/Mage.Sets/src/mage/cards/s/StrengthOfThePack.java index a47c7ba6c1f..6dd049b5d83 100644 --- a/Mage.Sets/src/mage/cards/s/StrengthOfThePack.java +++ b/Mage.Sets/src/mage/cards/s/StrengthOfThePack.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -7,7 +6,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -19,7 +18,7 @@ public final class StrengthOfThePack extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{G}{G}"); // Put two +1/+1 counters on each creature you control. - this.getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(2), new FilterControlledCreaturePermanent())); + this.getSpellAbility().addEffect(new AddCountersAllEffect(CounterType.P1P1.createInstance(2), StaticFilters.FILTER_CONTROLLED_CREATURE)); } private StrengthOfThePack(final StrengthOfThePack card) { @@ -30,4 +29,4 @@ public final class StrengthOfThePack extends CardImpl { public StrengthOfThePack copy() { return new StrengthOfThePack(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java b/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java index d1731451831..8af26b81087 100644 --- a/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java +++ b/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java @@ -70,7 +70,7 @@ class StrengthOfTheTajuruAddCountersTargetEffect extends OneShotEffect { int affectedTargets = 0; int amount = source.getManaCostsToPay().getX(); Counter counter = CounterType.P1P1.createInstance(amount); - for (UUID uuid : targetPointer.getTargets(game, source)) { + for (UUID uuid : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(uuid); if (permanent != null) { permanent.addCounters(counter.copy(), source.getControllerId(), source, game); diff --git a/Mage.Sets/src/mage/cards/s/StrixhavenStadium.java b/Mage.Sets/src/mage/cards/s/StrixhavenStadium.java index 8c36dd0c6aa..d44a036fd08 100644 --- a/Mage.Sets/src/mage/cards/s/StrixhavenStadium.java +++ b/Mage.Sets/src/mage/cards/s/StrixhavenStadium.java @@ -124,7 +124,7 @@ class StrixhavenStadiumEffect extends OneShotEffect { permanent.getCounters(game).getCount(CounterType.POINT) ), source, game); } - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.lost(game); return true; diff --git a/Mage.Sets/src/mage/cards/s/SufferThePast.java b/Mage.Sets/src/mage/cards/s/SufferThePast.java index 028bd98f1ee..4f2d3509d28 100644 --- a/Mage.Sets/src/mage/cards/s/SufferThePast.java +++ b/Mage.Sets/src/mage/cards/s/SufferThePast.java @@ -60,7 +60,7 @@ class SufferThePastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (you == null || targetPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SumalaRumblers.java b/Mage.Sets/src/mage/cards/s/SumalaRumblers.java new file mode 100644 index 00000000000..e54286b4573 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SumalaRumblers.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.MyriadAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class SumalaRumblers extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE); + + public SumalaRumblers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G/W}{G/W}"); + + this.subtype.add(SubType.WURM); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Sumala Rumblers's power is equal to the number of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(xValue) + .setText("{this}'s power is equal to the number of creatures you control"))); + + // Myriad + this.addAbility(new MyriadAbility()); + + } + + private SumalaRumblers(final SumalaRumblers card) { + super(card); + } + + @Override + public SumalaRumblers copy() { + return new SumalaRumblers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunCrestedPterodon.java b/Mage.Sets/src/mage/cards/s/SunCrestedPterodon.java index 6b485352234..1da9c7472ba 100644 --- a/Mage.Sets/src/mage/cards/s/SunCrestedPterodon.java +++ b/Mage.Sets/src/mage/cards/s/SunCrestedPterodon.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -18,7 +17,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -26,7 +25,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; */ public final class SunCrestedPterodon extends CardImpl { - private static final FilterControlledCreaturePermanent filterAnotherDino = new FilterControlledCreaturePermanent(); + private static final FilterControlledPermanent filterAnotherDino = new FilterControlledPermanent(); static { filterAnotherDino.add(AnotherPredicate.instance); filterAnotherDino.add(SubType.DINOSAUR.getPredicate()); @@ -60,4 +59,4 @@ public final class SunCrestedPterodon extends CardImpl { public SunCrestedPterodon copy() { return new SunCrestedPterodon(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SunkenHope.java b/Mage.Sets/src/mage/cards/s/SunkenHope.java index af14554b953..d0b88ed6ed0 100644 --- a/Mage.Sets/src/mage/cards/s/SunkenHope.java +++ b/Mage.Sets/src/mage/cards/s/SunkenHope.java @@ -10,12 +10,11 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.TargetController; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -60,12 +59,12 @@ class SunkenHopeReturnToHandEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { boolean result = false; - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } - Target target = new TargetControlledPermanent(1, 1, new FilterControlledCreaturePermanent(), true); + Target target = new TargetControlledCreaturePermanent().withNotTarget(true); if (target.canChoose(player.getId(), source, game)) { while (player.canRespond() && !target.isChosen() && target.canChoose(player.getId(), source, game)) { diff --git a/Mage.Sets/src/mage/cards/s/SunscorchedDivide.java b/Mage.Sets/src/mage/cards/s/SunscorchedDivide.java new file mode 100644 index 00000000000..f27bcda723a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunscorchedDivide.java @@ -0,0 +1,37 @@ +package mage.cards.s; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunscorchedDivide extends CardImpl { + + public SunscorchedDivide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {1}, {T}: Add {R}{W}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new Mana(1, 0, 0, 1, 0, 0, 0, 0), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private SunscorchedDivide(final SunscorchedDivide card) { + super(card); + } + + @Override + public SunscorchedDivide copy() { + return new SunscorchedDivide(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuperMutantScavenger.java b/Mage.Sets/src/mage/cards/s/SuperMutantScavenger.java new file mode 100644 index 00000000000..c4e9181ac72 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SuperMutantScavenger.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrDiesSourceTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SuperMutantScavenger extends CardImpl { + + private static final FilterCard filter = new FilterCard("Aura or Equipment card from your graveyard"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + } + + public SuperMutantScavenger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Super Mutant Scavenger enters the battlefield or dies, return up to one target Aura or Equipment card from your graveyard to your hand. + Ability ability = new EntersBattlefieldOrDiesSourceTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect(), false); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + this.addAbility(ability); + } + + private SuperMutantScavenger(final SuperMutantScavenger card) { + super(card); + } + + @Override + public SuperMutantScavenger copy() { + return new SuperMutantScavenger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SwanSong.java b/Mage.Sets/src/mage/cards/s/SwanSong.java index a0070265362..cd1bfd569d3 100644 --- a/Mage.Sets/src/mage/cards/s/SwanSong.java +++ b/Mage.Sets/src/mage/cards/s/SwanSong.java @@ -67,7 +67,7 @@ class SwanSongEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean countered = false; - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Spell spell = game.getStack().getSpell(targetId); if (game.getStack().counter(targetId, source, game)) { countered = true; diff --git a/Mage.Sets/src/mage/cards/s/SwarmOfRats.java b/Mage.Sets/src/mage/cards/s/SwarmOfRats.java index c50b65612f6..0e79c67a5e0 100644 --- a/Mage.Sets/src/mage/cards/s/SwarmOfRats.java +++ b/Mage.Sets/src/mage/cards/s/SwarmOfRats.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -11,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; /** * @@ -19,11 +18,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; */ public final class SwarmOfRats extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Rats you control"); - - static{ - filter.add(SubType.RAT.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.RAT, "Rats you control"); public SwarmOfRats(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); diff --git a/Mage.Sets/src/mage/cards/s/SwathcutterGiant.java b/Mage.Sets/src/mage/cards/s/SwathcutterGiant.java index b218e76e6f8..a39fb813f26 100644 --- a/Mage.Sets/src/mage/cards/s/SwathcutterGiant.java +++ b/Mage.Sets/src/mage/cards/s/SwathcutterGiant.java @@ -1,20 +1,16 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DamageAllEffect; -import mage.constants.SubType; +import mage.abilities.effects.common.DamageAllControlledTargetEffect; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -34,7 +30,9 @@ public final class SwathcutterGiant extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Whenever Swathcutter Giant attacks, it deals 1 damage to each creature defending player controls. - this.addAbility(new AttacksTriggeredAbility(new SwathcutterGiantEffect(), false)); + this.addAbility(new AttacksTriggeredAbility(new DamageAllControlledTargetEffect(1) + .setText("it deals 1 damage to each creature defending player controls"), + false, null, SetTargetPointer.PLAYER)); } private SwathcutterGiant(final SwathcutterGiant card) { @@ -46,30 +44,3 @@ public final class SwathcutterGiant extends CardImpl { return new SwathcutterGiant(this); } } - -class SwathcutterGiantEffect extends OneShotEffect { - - SwathcutterGiantEffect() { - super(Outcome.Benefit); - this.staticText = "it deals 1 damage to each creature " - + "defending player controls."; - } - - private SwathcutterGiantEffect(final SwathcutterGiantEffect effect) { - super(effect); - } - - @Override - public SwathcutterGiantEffect copy() { - return new SwathcutterGiantEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - FilterCreaturePermanent filter = new FilterCreaturePermanent(); - filter.add(new ControllerIdPredicate( - game.getCombat().getDefenderId(source.getSourceId()) - )); - return new DamageAllEffect(1, filter).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SweepAway.java b/Mage.Sets/src/mage/cards/s/SweepAway.java index 117f61621e2..c799bb00fb0 100644 --- a/Mage.Sets/src/mage/cards/s/SweepAway.java +++ b/Mage.Sets/src/mage/cards/s/SweepAway.java @@ -53,7 +53,7 @@ class SweepAwayEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && controller != null) { if (permanent.isAttacking()) { if (controller.chooseUse(Outcome.Neutral, "Put " + permanent.getIdName() + " on top of its owner's library (otherwise return to hand)?", source, game)) { diff --git a/Mage.Sets/src/mage/cards/s/SwordOfLightAndShadow.java b/Mage.Sets/src/mage/cards/s/SwordOfLightAndShadow.java index 144f302aa29..38fb707d58d 100644 --- a/Mage.Sets/src/mage/cards/s/SwordOfLightAndShadow.java +++ b/Mage.Sets/src/mage/cards/s/SwordOfLightAndShadow.java @@ -81,7 +81,7 @@ class SwordOfLightAndShadowEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); return controller != null && card != null && controller.moveCards(card, Zone.HAND, source, game); } } diff --git a/Mage.Sets/src/mage/cards/s/SyggRiverGuide.java b/Mage.Sets/src/mage/cards/s/SyggRiverGuide.java index 697f502b183..cf3651929ec 100644 --- a/Mage.Sets/src/mage/cards/s/SyggRiverGuide.java +++ b/Mage.Sets/src/mage/cards/s/SyggRiverGuide.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -11,9 +10,8 @@ import mage.abilities.keyword.IslandwalkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.Target; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetControlledPermanent; /** * @@ -21,11 +19,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class SyggRiverGuide extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Merfolk you control"); - - static { - filter.add(SubType.MERFOLK.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.MERFOLK, "Merfolk you control"); public SyggRiverGuide(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{U}"); @@ -40,8 +34,7 @@ public final class SyggRiverGuide extends CardImpl { this.addAbility(new IslandwalkAbility()); // {1}{W}: Target Merfolk you control gains protection from the color of your choice until end of turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainProtectionFromColorTargetEffect(Duration.EndOfTurn), new ManaCostsImpl<>("{1}{W}")); - Target target = new TargetControlledCreaturePermanent(1,1,filter, false); - ability.addTarget(target); + ability.addTarget(new TargetControlledPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TARDIS.java b/Mage.Sets/src/mage/cards/t/TARDIS.java new file mode 100644 index 00000000000..b4556aee1c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TARDIS.java @@ -0,0 +1,66 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.PlaneswalkEffect; +import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.CascadeAbility; +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.filter.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author Skiwkr + */ +public final class TARDIS extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.TIME_LORD); + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + + private static final Hint hint = new ConditionHint(condition, "You control a Time Lord"); + + public TARDIS(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + this.addAbility(FlyingAbility.getInstance()); + + // Whenever TARDIS attacks, if you control a Time Lord, the next spell you cast this turn has cascade and you may planeswalk. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new NextSpellCastHasAbilityEffect(new CascadeAbility()), false), + new PermanentsOnTheBattlefieldCondition(filter), + "Whenever {this} attacks, if you control a Time Lord, the next spell you cast this turn has cascade and you may planeswalk."); + + ability.addEffect(new PlaneswalkEffect(true)); + + ability.addHint(hint); + + this.addAbility(ability); + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private TARDIS(final TARDIS card) { + super(card); + } + + @Override + public TARDIS copy() { + return new TARDIS(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java b/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java index 2794e7fc06e..c0176115730 100644 --- a/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java +++ b/Mage.Sets/src/mage/cards/t/TadeasJuniperAscendant.java @@ -110,7 +110,7 @@ class TadeasJuniperAscendantEvasionEffect extends RestrictionEffect { TadeasJuniperAscendantEvasionEffect(TargetPointer targetPointer) { super(Duration.EndOfCombat); - this.targetPointer = targetPointer; + this.setTargetPointer(targetPointer); staticText = "and can't be blocked by creatures with greater power"; } @@ -120,7 +120,7 @@ class TadeasJuniperAscendantEvasionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TahngarthFirstMate.java b/Mage.Sets/src/mage/cards/t/TahngarthFirstMate.java index aaff03f92f0..2f4ba15cb0e 100644 --- a/Mage.Sets/src/mage/cards/t/TahngarthFirstMate.java +++ b/Mage.Sets/src/mage/cards/t/TahngarthFirstMate.java @@ -14,12 +14,14 @@ import mage.constants.*; import mage.filter.common.FilterPlayerOrPlaneswalker; import mage.filter.predicate.Predicate; import mage.game.Game; +import mage.game.combat.CombatGroup; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.targetpointer.FixedTarget; +import java.util.Objects; import java.util.UUID; /** @@ -143,7 +145,12 @@ enum TahngarthFirstMatePlayerPredicate implements Predicate { @Override public boolean apply(Player input, Game game) { - return game.getCombat().getDefenders().contains(input.getId()); + return game.getCombat() + .getGroups() + .stream() + .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) + .anyMatch(id -> id.equals(input.getId())); } } @@ -152,6 +159,11 @@ enum TahngarthFirstMatePermanentPredicate implements Predicate { @Override public boolean apply(Permanent input, Game game) { - return game.getCombat().getDefenders().contains(input.getId()); + return game.getCombat() + .getGroups() + .stream() + .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) + .anyMatch(id -> id.equals(input.getId())); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TaintedStrike.java b/Mage.Sets/src/mage/cards/t/TaintedStrike.java index 0fc78859de0..fe6b93ea4e2 100644 --- a/Mage.Sets/src/mage/cards/t/TaintedStrike.java +++ b/Mage.Sets/src/mage/cards/t/TaintedStrike.java @@ -21,8 +21,11 @@ public final class TaintedStrike extends CardImpl { public TaintedStrike (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}"); - this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0, Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new GainAbilityTargetEffect(InfectAbility.getInstance(), Duration.EndOfTurn)); + // Target creature gets +1/+0 and gains infect until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0, Duration.EndOfTurn) + .setText("target creature gets +1/+0")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(InfectAbility.getInstance(), Duration.EndOfTurn) + .setText("and gains infect until end of turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/t/TakeInventory.java b/Mage.Sets/src/mage/cards/t/TakeInventory.java index 7acfd78ae7c..f3c89ded44a 100644 --- a/Mage.Sets/src/mage/cards/t/TakeInventory.java +++ b/Mage.Sets/src/mage/cards/t/TakeInventory.java @@ -5,6 +5,8 @@ import java.util.UUID; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -14,6 +16,7 @@ import mage.filter.predicate.mageobject.NamePredicate; /** * * @author fireshoes + * modified tiera3 - added Hint */ public final class TakeInventory extends CardImpl { @@ -22,6 +25,9 @@ public final class TakeInventory extends CardImpl { static { filter.add(new NamePredicate("Take Inventory")); } + private static final Hint hint = new ValueHint( + "Cards named Take Inventory in your graveyard", new CardsInControllerGraveyardCount(filter) + ); public TakeInventory(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{U}"); @@ -31,6 +37,7 @@ public final class TakeInventory extends CardImpl { Effect effect = new DrawCardSourceControllerEffect(new CardsInControllerGraveyardCount(filter)); effect.setText(", then draw cards equal to the number of cards named {this} in your graveyard"); this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addHint(hint); } private TakeInventory(final TakeInventory card) { diff --git a/Mage.Sets/src/mage/cards/t/TakenoSamuraiGeneral.java b/Mage.Sets/src/mage/cards/t/TakenoSamuraiGeneral.java index 9aadee1ce13..bec07c36b24 100644 --- a/Mage.Sets/src/mage/cards/t/TakenoSamuraiGeneral.java +++ b/Mage.Sets/src/mage/cards/t/TakenoSamuraiGeneral.java @@ -1,5 +1,3 @@ - - package mage.cards.t; import java.util.Iterator; @@ -47,13 +45,14 @@ public final class TakenoSamuraiGeneral extends CardImpl { } class TakenoSamuraiGeneralEffect extends ContinuousEffectImpl { + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); static { filter.add(SubType.SAMURAI.getPredicate()); } - public TakenoSamuraiGeneralEffect() { + TakenoSamuraiGeneralEffect() { super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.BoostCreature); staticText = "Each other Samurai creature you control gets +1/+1 for each point of bushido it has"; } diff --git a/Mage.Sets/src/mage/cards/t/TalentOfTheTelepath.java b/Mage.Sets/src/mage/cards/t/TalentOfTheTelepath.java index 4b580575a91..b01e2fbca85 100644 --- a/Mage.Sets/src/mage/cards/t/TalentOfTheTelepath.java +++ b/Mage.Sets/src/mage/cards/t/TalentOfTheTelepath.java @@ -74,7 +74,7 @@ class TalentOfTheTelepathEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TanazirQuandrix.java b/Mage.Sets/src/mage/cards/t/TanazirQuandrix.java index 51d1437b804..a1cad89c018 100644 --- a/Mage.Sets/src/mage/cards/t/TanazirQuandrix.java +++ b/Mage.Sets/src/mage/cards/t/TanazirQuandrix.java @@ -7,7 +7,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; import mage.abilities.dynamicvalue.common.SourcePermanentToughnessValue; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TrampleAbility; @@ -16,8 +16,6 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -45,7 +43,9 @@ public final class TanazirQuandrix extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // When Tanazir Quandrix enters the battlefield, double the number of +1/+1 counters on target creature you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new TanazirQuandrixEffect()); + Ability ability = new EntersBattlefieldTriggeredAbility(new DoubleCountersTargetEffect(CounterType.P1P1) + .setText("double the number of +1/+1 counters on target creature you control") + ); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); @@ -66,32 +66,3 @@ public final class TanazirQuandrix extends CardImpl { return new TanazirQuandrix(this); } } - -class TanazirQuandrixEffect extends OneShotEffect { - - TanazirQuandrixEffect() { - super(Outcome.Benefit); - staticText = "double the number of +1/+1 counters on target creature you control"; - } - - private TanazirQuandrixEffect(final TanazirQuandrixEffect effect) { - super(effect); - } - - @Override - public TanazirQuandrixEffect copy() { - return new TanazirQuandrixEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent == null) { - return false; - } - int counterCount = permanent.getCounters(game).getCount(CounterType.P1P1); - return counterCount > 0 && permanent.addCounters( - CounterType.P1P1.createInstance(counterCount), source.getControllerId(), source, game - ); - } -} diff --git a/Mage.Sets/src/mage/cards/t/TangleWire.java b/Mage.Sets/src/mage/cards/t/TangleWire.java index a5cdfcd7c09..e5cd4370c41 100644 --- a/Mage.Sets/src/mage/cards/t/TangleWire.java +++ b/Mage.Sets/src/mage/cards/t/TangleWire.java @@ -68,7 +68,7 @@ class TangleWireEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (player == null || permanent == null) { return false; diff --git a/Mage.Sets/src/mage/cards/t/TangletroveKelp.java b/Mage.Sets/src/mage/cards/t/TangletroveKelp.java new file mode 100644 index 00000000000..01a59bd6679 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TangletroveKelp.java @@ -0,0 +1,100 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.WardAbility; +import mage.abilities.token.ClueAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class TangletroveKelp extends CardImpl { + + public TangletroveKelp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}{U}{U}"); + this.subtype.add(SubType.CLUE, SubType.PLANT); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Ward {2} + this.addAbility(new WardAbility(new GenericManaCost(2), false)); + + // At the beginning of each combat, other Clues you control become 6/6 Plant creatures in addition to their other types until end of turn. + this.addAbility(new BeginningOfCombatTriggeredAbility(new TangletroveKelpEffect(), TargetController.ANY, false)); + + // {2}, Sacrifice Tangletrove Kelp: Draw a card. + this.addAbility(new ClueAbility(true)); + } + + private TangletroveKelp(final TangletroveKelp card) { + super(card); + } + + @Override + public TangletroveKelp copy() { + return new TangletroveKelp(this); + } +} + +class TangletroveKelpEffect extends ContinuousEffectImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("other Clues you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(SubType.CLUE.getPredicate()); + } + + TangletroveKelpEffect() { + super(Duration.EndOfTurn, Outcome.BecomeCreature); + staticText = "other Clues you control become 6/6 Plant creatures in addition to their other types until end of turn"; + } + + private TangletroveKelpEffect(final TangletroveKelpEffect effect) { + super(effect); + } + + @Override + public TangletroveKelpEffect copy() { + return new TangletroveKelpEffect(this); + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.TypeChangingEffects_4 + || layer == Layer.PTChangingEffects_7; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (Permanent clue : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + switch (layer) { + case TypeChangingEffects_4: + clue.addCardType(game, CardType.CREATURE); + clue.addSubType(game, SubType.PLANT); + break; + case PTChangingEffects_7: + clue.getToughness().setModifiedBaseValue(6); + clue.getPower().setModifiedBaseValue(6); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TarPitcher.java b/Mage.Sets/src/mage/cards/t/TarPitcher.java index d48bf240c71..c2404286e2a 100644 --- a/Mage.Sets/src/mage/cards/t/TarPitcher.java +++ b/Mage.Sets/src/mage/cards/t/TarPitcher.java @@ -1,4 +1,3 @@ - package mage.cards.t; import java.util.UUID; @@ -13,8 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetAnyTarget; /** @@ -23,11 +21,7 @@ import mage.target.common.TargetAnyTarget; */ public final class TarPitcher extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Goblin"); - - static { - filter.add(SubType.GOBLIN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GOBLIN, "Goblin"); public TarPitcher(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}"); diff --git a/Mage.Sets/src/mage/cards/t/TasteOfDeath.java b/Mage.Sets/src/mage/cards/t/TasteOfDeath.java index 21cb865cc0f..001c7214a83 100644 --- a/Mage.Sets/src/mage/cards/t/TasteOfDeath.java +++ b/Mage.Sets/src/mage/cards/t/TasteOfDeath.java @@ -5,8 +5,7 @@ import mage.abilities.effects.common.SacrificeAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.game.permanent.token.FoodToken; import java.util.UUID; @@ -16,13 +15,11 @@ import java.util.UUID; */ public final class TasteOfDeath extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("creatures"); - public TasteOfDeath(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); // Each player sacrifices three creatures. You create three Food tokens. - this.getSpellAbility().addEffect(new SacrificeAllEffect(3, filter)); + this.getSpellAbility().addEffect(new SacrificeAllEffect(3, StaticFilters.FILTER_PERMANENT_CREATURES)); this.getSpellAbility().addEffect(new CreateTokenEffect(new FoodToken(), 3).concatBy("You")); } diff --git a/Mage.Sets/src/mage/cards/t/TeferisPuzzleBox.java b/Mage.Sets/src/mage/cards/t/TeferisPuzzleBox.java index 178f2090eab..c5a767f1e48 100644 --- a/Mage.Sets/src/mage/cards/t/TeferisPuzzleBox.java +++ b/Mage.Sets/src/mage/cards/t/TeferisPuzzleBox.java @@ -51,7 +51,7 @@ class TeferisPuzzleBoxEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int count = player.getHand().size(); player.putCardsOnBottomOfLibrary(player.getHand(), game, source, true); diff --git a/Mage.Sets/src/mage/cards/t/TempleOfCivilization.java b/Mage.Sets/src/mage/cards/t/TempleOfCivilization.java index 2fa4a1e9f63..ee18a2fb64a 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfCivilization.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfCivilization.java @@ -35,7 +35,7 @@ public final class TempleOfCivilization extends CardImpl { Ability ability = new ActivateIfConditionActivatedAbility( Zone.BATTLEFIELD, new TransformSourceEffect(), - new ManaCostsImpl("{2}{W}"), + new ManaCostsImpl<>("{2}{W}"), TempleOfCivilizationCondition.instance, TimingRule.SORCERY ); diff --git a/Mage.Sets/src/mage/cards/t/TempleOfCultivation.java b/Mage.Sets/src/mage/cards/t/TempleOfCultivation.java index b1d9b365e29..60d3c9327a3 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfCultivation.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfCultivation.java @@ -37,7 +37,7 @@ public final class TempleOfCultivation extends CardImpl { Ability ability = new ActivateIfConditionActivatedAbility( Zone.BATTLEFIELD, new TransformSourceEffect(), - new ManaCostsImpl("{2}{G}"), + new ManaCostsImpl<>("{2}{G}"), new TempleOfCultivationCondition(), TimingRule.SORCERY ); diff --git a/Mage.Sets/src/mage/cards/t/TempleOfTheDead.java b/Mage.Sets/src/mage/cards/t/TempleOfTheDead.java index 9e326692c93..7c0bf16f60b 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfTheDead.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfTheDead.java @@ -35,7 +35,7 @@ public final class TempleOfTheDead extends CardImpl { Ability ability = new ActivateIfConditionActivatedAbility( Zone.BATTLEFIELD, new TransformSourceEffect(), - new ManaCostsImpl("{2}{B}"), + new ManaCostsImpl<>("{2}{B}"), TempleOfTheDeadCondition.instance, TimingRule.SORCERY ); diff --git a/Mage.Sets/src/mage/cards/t/TemptWithReflections.java b/Mage.Sets/src/mage/cards/t/TemptWithReflections.java index 8f9cb0d08ae..54f427533a8 100644 --- a/Mage.Sets/src/mage/cards/t/TemptWithReflections.java +++ b/Mage.Sets/src/mage/cards/t/TemptWithReflections.java @@ -62,7 +62,7 @@ class TemptWithReflectionsEffect extends OneShotEffect { Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); if (permanent != null) { Effect effect = new CreateTokenCopyTargetEffect(); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); Set playersSaidYes = new HashSet<>(); @@ -85,12 +85,12 @@ class TemptWithReflectionsEffect extends OneShotEffect { for (UUID playerId : playersSaidYes) { effect = new CreateTokenCopyTargetEffect(playerId); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); // create a token for the source controller as well effect = new CreateTokenCopyTargetEffect(); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/t/Tenacity.java b/Mage.Sets/src/mage/cards/t/Tenacity.java index 13cd203e867..cf517daa8e3 100644 --- a/Mage.Sets/src/mage/cards/t/Tenacity.java +++ b/Mage.Sets/src/mage/cards/t/Tenacity.java @@ -1,4 +1,3 @@ - package mage.cards.t; import java.util.UUID; @@ -12,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; /** * @@ -27,7 +25,7 @@ public final class Tenacity extends CardImpl { Effect boost = new BoostControlledEffect(1, 1, Duration.EndOfTurn); boost.setText("Creatures you control get +1/+1"); this.getSpellAbility().addEffect(boost); - this.getSpellAbility().addEffect(new GainAbilityAllEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent(), " and gain lifelink until end of turn")); + this.getSpellAbility().addEffect(new GainAbilityAllEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES, " and gain lifelink until end of turn")); this.getSpellAbility().addEffect(new UntapAllControllerEffect(StaticFilters.FILTER_PERMANENT_CREATURE, "Untap those creatures")); } diff --git a/Mage.Sets/src/mage/cards/t/TenebTheHarvester.java b/Mage.Sets/src/mage/cards/t/TenebTheHarvester.java index 4677a22e4ed..c8ba5e9d020 100644 --- a/Mage.Sets/src/mage/cards/t/TenebTheHarvester.java +++ b/Mage.Sets/src/mage/cards/t/TenebTheHarvester.java @@ -14,6 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.Target; import mage.target.common.TargetCardInGraveyard; @@ -37,7 +38,7 @@ public final class TenebTheHarvester extends CardImpl { // Whenever Teneb, the Harvester deals combat damage to a player, you may pay {2}{B}. If you do, put target creature card from a graveyard onto the battlefield under your control. Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility( new DoIfCostPaid(new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{2}{B}")), false); - Target target = new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard")); + Target target = new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD); ability.addTarget(target); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TenthDistrictHero.java b/Mage.Sets/src/mage/cards/t/TenthDistrictHero.java new file mode 100644 index 00000000000..bc6d8af9fe0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TenthDistrictHero.java @@ -0,0 +1,140 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.AddCardSubTypeSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TenthDistrictHero extends CardImpl { + + public TenthDistrictHero(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {1}{W}, Collect evidence 2: Tenth District Hero becomes a Human Detective with base power and toughness 4/4 and gains vigilance. + Ability ability = new SimpleActivatedAbility(new AddCardSubTypeSourceEffect( + Duration.Custom, SubType.HUMAN, SubType.DETECTIVE + ), new ManaCostsImpl<>("{1}{W}")); + ability.addCost(new CollectEvidenceCost(2)); + ability.addEffect(new SetBasePowerToughnessSourceEffect( + 4, 4, Duration.Custom + ).setText("with base power and toughness 4/4")); + ability.addEffect(new GainAbilitySourceEffect( + VigilanceAbility.getInstance(), Duration.Custom + ).setText("and gains vigilance")); + this.addAbility(ability); + + // {2}{W}, Collect evidence 4: If Tenth District Hero is a Detective, it becomes a legendary creature named Mileva, the Stalwart, it has base power and toughness 5/5, and it gains "Other creatures you control have indestructible." + ability = new SimpleActivatedAbility(new TenthDistrictHeroEffect(), new ManaCostsImpl<>("{2}{W}")); + ability.addCost(new CollectEvidenceCost(4)); + this.addAbility(ability); + } + + private TenthDistrictHero(final TenthDistrictHero card) { + super(card); + } + + @Override + public TenthDistrictHero copy() { + return new TenthDistrictHero(this); + } +} + +class TenthDistrictHeroEffect extends ContinuousEffectImpl { + + TenthDistrictHeroEffect() { + super(Duration.Custom, Outcome.Benefit); + staticText = "if {this} is a Detective, it becomes a legendary creature " + + "named Mileva, the Stalwart, it has base power and toughness 5/5, " + + "and it gains \"Other creatures you control have indestructible.\""; + } + + private TenthDistrictHeroEffect(final TenthDistrictHeroEffect effect) { + super(effect); + } + + @Override + public TenthDistrictHeroEffect copy() { + return new TenthDistrictHeroEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || !permanent.hasSubtype(SubType.DETECTIVE, game)) { + discard(); + } + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + discard(); + return false; + } + switch (layer) { + case TextChangingEffects_3: + permanent.setName("Mileva, the Stalwart"); + return true; + case TypeChangingEffects_4: + permanent.addSuperType(game, SuperType.LEGENDARY); + permanent.addCardType(game, CardType.CREATURE); + return true; + case AbilityAddingRemovingEffects_6: + permanent.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES, true + )), source.getSourceId(), game); + return true; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + permanent.getPower().setModifiedBaseValue(5); + permanent.getToughness().setModifiedBaseValue(5); + return true; + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case TextChangingEffects_3: + case TypeChangingEffects_4: + case AbilityAddingRemovingEffects_6: + case PTChangingEffects_7: + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/t/Terastodon.java b/Mage.Sets/src/mage/cards/t/Terastodon.java index e84ee1cf101..e314d78c2f1 100644 --- a/Mage.Sets/src/mage/cards/t/Terastodon.java +++ b/Mage.Sets/src/mage/cards/t/Terastodon.java @@ -77,7 +77,7 @@ class TerastodonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Map destroyedPermanents = new HashMap<>(); - for (UUID targetID : this.targetPointer.getTargets(game, source)) { + for (UUID targetID : this.getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetID); if (permanent != null) { if (permanent.destroy(source, game, false)) { diff --git a/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java b/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java index acc83f05386..6be4b3fb057 100644 --- a/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java +++ b/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java @@ -156,7 +156,7 @@ class TergridGodOfFrightEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { // controller gets to choose the order in which the cards enter the battlefield controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/t/Terraformer.java b/Mage.Sets/src/mage/cards/t/Terraformer.java index a10fd01ad7c..49d3c09ee84 100644 --- a/Mage.Sets/src/mage/cards/t/Terraformer.java +++ b/Mage.Sets/src/mage/cards/t/Terraformer.java @@ -98,6 +98,11 @@ class TerraformerContinuousEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + "_Terraformer")); + if (choice == null) { + discard(); + return; + } + switch (choice) { case FOREST: dependencyTypes.add(DependencyType.BecomeForest); diff --git a/Mage.Sets/src/mage/cards/t/TesakJudithsHellhound.java b/Mage.Sets/src/mage/cards/t/TesakJudithsHellhound.java new file mode 100644 index 00000000000..372199e9f18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TesakJudithsHellhound.java @@ -0,0 +1,78 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; +import mage.abilities.effects.mana.DynamicManaEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.UnleashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.permanent.CounterAnyPredicate; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class TesakJudithsHellhound extends CardImpl { + + private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent(SubType.DOG, "Dogs"); + private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("Creatures you control with counters on them"); + private static final FilterNonlandCard filter3 = new FilterNonlandCard(); + + static { + filter2.add(CounterAnyPredicate.instance); + filter3.add(SubType.DOG.getPredicate()); + } + + public TesakJudithsHellhound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL, SubType.DOG); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Unleash + this.addAbility(new UnleashAbility()); + + // Other Dogs you control have unleash. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + new UnleashAbility(), Duration.WhileOnBattlefield, filter1, true + )); + ability.addEffect(new GainAbilityControlledSpellsEffect(new UnleashAbility(), filter3).setText("")); + this.addAbility(ability); + + // Creatures you control with counters on them have haste. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter2 + ))); + + // Whenever Tesak, Judith's Hellhound attacks, add {R} for each attacking creature. + this.addAbility(new AttacksTriggeredAbility( + new DynamicManaEffect(Mana.RedMana(1), new PermanentsOnBattlefieldCount(StaticFilters.FILTER_ATTACKING_CREATURE)) + )); + } + + private TesakJudithsHellhound(final TesakJudithsHellhound card) { + super(card); + } + + @Override + public TesakJudithsHellhound copy() { + return new TesakJudithsHellhound(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TezzeretCruelMachinist.java b/Mage.Sets/src/mage/cards/t/TezzeretCruelMachinist.java index e7b935cee74..02d7ba47038 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretCruelMachinist.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretCruelMachinist.java @@ -97,7 +97,7 @@ class TezzeretCruelMachinistEffect extends OneShotEffect { .getCards(game) .stream() .map(card -> new MageObjectReference(card, game, 1)) - .collect(Collectors.toSet()), game + .collect(Collectors.toList()) )), source); player.moveCards( cardsToMove.getCards(game), Zone.BATTLEFIELD, source, game, @@ -125,7 +125,7 @@ class TezzeretCruelMachinistCardTypeEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { boolean flag = false; - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent target = game.getPermanent(targetId); if (target == null || !target.isFaceDown(game)) { continue; diff --git a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfMetal.java b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfMetal.java index 5eb38ddaf69..0f397d961bc 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfMetal.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfMetal.java @@ -84,7 +84,7 @@ class TezzeretMasterOfMetalEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - List permanents = game.getBattlefield().getAllActivePermanents(filter, targetPointer.getFirst(game, source), game); + List permanents = game.getBattlefield().getAllActivePermanents(filter, getTargetPointer().getFirst(game, source), game); for (Permanent permanent : permanents) { ContinuousEffect effect = new TezzeretMasterOfMetalControlEffect(source.getControllerId()); effect.setTargetPointer(new FixedTarget(permanent, game)); @@ -115,7 +115,7 @@ class TezzeretMasterOfMetalControlEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && controllerId != null) { return permanent.changeControllerId(controllerId, game, source); } diff --git a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java index a38b59f18d3..4dc3e1202ce 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java @@ -12,8 +12,8 @@ import mage.abilities.hint.common.ArtifactYouControlHint; import mage.abilities.keyword.AffinityForArtifactsAbility; import mage.cards.*; import mage.constants.*; -import mage.filter.FilterCard; import mage.filter.StaticFilters; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; @@ -26,7 +26,7 @@ import java.util.UUID; */ public final class TezzeretMasterOfTheBridge extends CardImpl { - private static final FilterCard filter = new FilterCard("creature and planeswalker spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("creature and planeswalker spells you cast"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java b/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java index d25c22ccbdc..5607df7d485 100644 --- a/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java +++ b/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java @@ -64,7 +64,7 @@ class ThadaAdelAcquisitorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || damagedPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/ThallidDevourer.java b/Mage.Sets/src/mage/cards/t/ThallidDevourer.java index 0f727aa27b4..74c848cfb7d 100644 --- a/Mage.Sets/src/mage/cards/t/ThallidDevourer.java +++ b/Mage.Sets/src/mage/cards/t/ThallidDevourer.java @@ -14,9 +14,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -24,10 +23,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class ThallidDevourer extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Saproling"); - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SAPROLING, "Saproling"); public ThallidDevourer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/t/ThallidGerminator.java b/Mage.Sets/src/mage/cards/t/ThallidGerminator.java index 6e19f3d1a5f..d4cdcd42b43 100644 --- a/Mage.Sets/src/mage/cards/t/ThallidGerminator.java +++ b/Mage.Sets/src/mage/cards/t/ThallidGerminator.java @@ -15,9 +15,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -26,10 +25,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class ThallidGerminator extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Saproling"); - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SAPROLING, "Saproling"); public ThallidGerminator(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/t/TheBattleOfEndor.java b/Mage.Sets/src/mage/cards/t/TheBattleOfEndor.java index b37834b4724..a297e52f374 100644 --- a/Mage.Sets/src/mage/cards/t/TheBattleOfEndor.java +++ b/Mage.Sets/src/mage/cards/t/TheBattleOfEndor.java @@ -1,4 +1,3 @@ - package mage.cards.t; import java.util.UUID; @@ -16,6 +15,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -38,10 +38,10 @@ public final class TheBattleOfEndor extends CardImpl { this.getSpellAbility().addEffect(new TheBattleOfEndorEffect()); // Creatures you control gain trample and haste until end of turn. - Effect effect = new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent()); + Effect effect = new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES); effect.setText("Creatures you control gain trample"); this.getSpellAbility().addEffect(effect); - effect = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent()); + effect = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES); effect.setText("and haste until end of turn"); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/t/TheBlackGate.java b/Mage.Sets/src/mage/cards/t/TheBlackGate.java index deabaa52370..43b131be0ef 100644 --- a/Mage.Sets/src/mage/cards/t/TheBlackGate.java +++ b/Mage.Sets/src/mage/cards/t/TheBlackGate.java @@ -53,7 +53,7 @@ public final class TheBlackGate extends CardImpl { // {1}{B}, {T}: Choose a player with the most life or tied for most life. Target creature can't be blocked by creatures that player controls this turn. ActivatedAbility ability = new SimpleActivatedAbility( new BlackGateEffect(), - new ManaCostsImpl("{1}{B}") + new ManaCostsImpl<>("{1}{B}") ); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent()); @@ -148,4 +148,4 @@ class BlackGateEffect extends OneShotEffect { return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TheCrueltyOfGix.java b/Mage.Sets/src/mage/cards/t/TheCrueltyOfGix.java index c6db012b9aa..0f19e8d3931 100644 --- a/Mage.Sets/src/mage/cards/t/TheCrueltyOfGix.java +++ b/Mage.Sets/src/mage/cards/t/TheCrueltyOfGix.java @@ -13,6 +13,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInGraveyard; @@ -26,7 +27,6 @@ import mage.target.common.TargetOpponent; public final class TheCrueltyOfGix extends CardImpl { private static final FilterCard filter = new FilterCard("creature or planeswalker card"); - private static final FilterCreatureCard filter2 = new FilterCreatureCard("creature card from a graveyard"); static { filter.add(Predicates.or( @@ -60,7 +60,7 @@ public final class TheCrueltyOfGix extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_III, SagaChapter.CHAPTER_III, new ReturnFromGraveyardToBattlefieldTargetEffect(), - new TargetCardInGraveyard(filter2) + new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD) ); this.addAbility(sagaAbility); } diff --git a/Mage.Sets/src/mage/cards/t/TheEnigmaJewel.java b/Mage.Sets/src/mage/cards/t/TheEnigmaJewel.java index c3a7d1a8c6f..a7a10d1f583 100644 --- a/Mage.Sets/src/mage/cards/t/TheEnigmaJewel.java +++ b/Mage.Sets/src/mage/cards/t/TheEnigmaJewel.java @@ -3,9 +3,7 @@ package mage.cards.t; import mage.ConditionalMana; import mage.MageObject; import mage.Mana; -import mage.abilities.Abilities; import mage.abilities.Ability; -import mage.abilities.ActivatedAbilityImpl; import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.Cost; @@ -21,7 +19,6 @@ import mage.constants.CardType; import mage.constants.SuperType; import mage.filter.predicate.Predicate; import mage.game.Game; -import mage.game.permanent.Permanent; import java.util.UUID; @@ -66,20 +63,12 @@ enum TheEnigmaJewelPredicate implements Predicate { @Override public boolean apply(MageObject input, Game game) { return !input.isLand(game) - && getAbilities(input, game) + && input instanceof Card + && ((Card) input).getAbilities(game) .stream() - .anyMatch(ActivatedAbilityImpl.class::isInstance); + .anyMatch(a -> (a.getAbilityType() == AbilityType.ACTIVATED || a.getAbilityType() == AbilityType.MANA)); } - private static Abilities getAbilities(MageObject input, Game game) { - if (input instanceof Permanent) { - return ((Permanent) input).getAbilities(game); - } else if (input instanceof Card) { - return ((Card) input).getAbilities(game); - } else { - throw new UnsupportedOperationException("there shouldn't be a nonpermanent, noncard object here"); - } - } } class TheEnigmaJewelManaBuilder extends ConditionalManaBuilder { diff --git a/Mage.Sets/src/mage/cards/t/TheFirstSliver.java b/Mage.Sets/src/mage/cards/t/TheFirstSliver.java index ebd21c23252..5b30ee08248 100644 --- a/Mage.Sets/src/mage/cards/t/TheFirstSliver.java +++ b/Mage.Sets/src/mage/cards/t/TheFirstSliver.java @@ -9,7 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import java.util.UUID; @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class TheFirstSliver extends CardImpl { - private static final FilterCard filter = new FilterCard("Sliver spells you cast"); + private static final FilterNonlandCard filter = new FilterNonlandCard("Sliver spells you cast"); static { filter.add(SubType.SLIVER.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/t/TheFourteenthDoctor.java b/Mage.Sets/src/mage/cards/t/TheFourteenthDoctor.java new file mode 100644 index 00000000000..b6c83ee5ef0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheFourteenthDoctor.java @@ -0,0 +1,184 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheFourteenthDoctor extends CardImpl { + + public TheFourteenthDoctor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R/G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.DOCTOR); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When you cast this spell, reveal the top fourteen cards of your library. Put all Doctor cards revealed this way into your graveyard and the rest on the bottom of your library in a random order. + this.addAbility(new CastSourceTriggeredAbility(new TheFourteenthDoctorRevealEffect())); + + // You may have The Fourteenth Doctor enter the battlefield as a copy of a Doctor card in your graveyard that was put there from your library this turn. If you do, it gains haste until end of turn. + this.addAbility(new EntersBattlefieldAbility(new TheFourteenthDoctorCopyEffect(), true)); + } + + private TheFourteenthDoctor(final TheFourteenthDoctor card) { + super(card); + } + + @Override + public TheFourteenthDoctor copy() { + return new TheFourteenthDoctor(this); + } +} + +class TheFourteenthDoctorRevealEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard(); + + static { + filter.add(SubType.DOCTOR.getPredicate()); + } + + TheFourteenthDoctorRevealEffect() { + super(Outcome.Benefit); + staticText = "reveal the top fourteen cards of your library. Put all Doctor cards revealed this way " + + "into your graveyard and the rest on the bottom of your library in a random order"; + } + + private TheFourteenthDoctorRevealEffect(final TheFourteenthDoctorRevealEffect effect) { + super(effect); + } + + @Override + public TheFourteenthDoctorRevealEffect copy() { + return new TheFourteenthDoctorRevealEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 14)); + player.revealCards(source, cards, game); + Cards toGrave = new CardsImpl(cards.getCards(filter, game)); + player.moveCards(toGrave, Zone.GRAVEYARD, source, game); + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } +} + +class TheFourteenthDoctorCopyEffect extends OneShotEffect { + + private enum TheFourteenthDoctorPredicate implements Predicate { + instance; + + @Override + public boolean apply(Card input, Game game) { + return TheFourteenthDoctorWatcher.checkCard(input, game); + } + } + + private static final FilterCard filter + = new FilterCard("Doctor card in your graveyard that was put there from your library this turn"); + + static { + filter.add(SubType.DOCTOR.getPredicate()); + filter.add(TheFourteenthDoctorPredicate.instance); + } + + TheFourteenthDoctorCopyEffect() { + super(Outcome.Benefit); + staticText = "as a copy of a Doctor card in your graveyard that was put there " + + "from your library this turn. If you do, it gains haste until end of turn"; + } + + private TheFourteenthDoctorCopyEffect(final TheFourteenthDoctorCopyEffect effect) { + super(effect); + } + + @Override + public TheFourteenthDoctorCopyEffect copy() { + return new TheFourteenthDoctorCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Target target = new TargetCardInYourGraveyard(0, 1, filter, true); + player.choose(outcome, target, source, game); + Card copyFromCard = game.getCard(target.getFirstTarget()); + if (copyFromCard == null) { + return false; + } + CopyEffect copyEffect = new CopyEffect(Duration.Custom, copyFromCard, source.getSourceId()); + game.addEffect(copyEffect, source); + game.addEffect(new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.EndOfTurn), source); + return true; + } +} + +class TheFourteenthDoctorWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + TheFourteenthDoctorWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ZONE_CHANGE) { + return; + } + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() == Zone.GRAVEYARD && zEvent.getFromZone() == Zone.LIBRARY) { + set.add(new MageObjectReference(zEvent.getTargetId(), game)); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkCard(Card card, Game game) { + return game + .getState() + .getWatcher(TheFourteenthDoctorWatcher.class) + .set + .stream() + .anyMatch(mor -> mor.refersTo(card, game)); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheFugitiveDoctor.java b/Mage.Sets/src/mage/cards/t/TheFugitiveDoctor.java index 532e1c538e2..775e4279c8e 100644 --- a/Mage.Sets/src/mage/cards/t/TheFugitiveDoctor.java +++ b/Mage.Sets/src/mage/cards/t/TheFugitiveDoctor.java @@ -16,7 +16,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.target.common.TargetCardInYourGraveyard; @@ -27,8 +26,6 @@ import java.util.UUID; */ public final class TheFugitiveDoctor extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CLUE, "a Clue"); - public TheFugitiveDoctor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}"); @@ -45,7 +42,7 @@ public final class TheFugitiveDoctor extends CardImpl { ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new TheFugitiveDoctorEffect(), false); ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY)); this.addAbility(new AttacksTriggeredAbility(new DoWhenCostPaid( - ability, new SacrificeTargetCost(filter), "Sacrifice a Clue?" + ability, new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CLUE), "Sacrifice a Clue?" ))); } @@ -77,7 +74,7 @@ class TheFugitiveDoctorEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TheMeep.java b/Mage.Sets/src/mage/cards/t/TheMeep.java new file mode 100644 index 00000000000..978dd16c1ad --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMeep.java @@ -0,0 +1,93 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMeep extends CardImpl { + + public TheMeep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ALIEN); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Ward--Pay 3 life. + this.addAbility(new WardAbility(new PayLifeCost(3))); + + // Whenever The Meep attacks, you may sacrifice another creature. If you do, creatures you control have base power and toughness X/X until end of turn, where X is the sacrificed creature's mana value. + this.addAbility(new AttacksTriggeredAbility(new TheMeepEffect())); + } + + private TheMeep(final TheMeep card) { + super(card); + } + + @Override + public TheMeep copy() { + return new TheMeep(this); + } +} + +class TheMeepEffect extends OneShotEffect { + + TheMeepEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice another creature. If you do, creatures you control have " + + "base power and toughness X/X until end of turn, where X is the sacrificed creature's mana value"; + } + + private TheMeepEffect(final TheMeepEffect effect) { + super(effect); + } + + @Override + public TheMeepEffect copy() { + return new TheMeepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE); + if (!cost.canPay(source, source, source.getControllerId(), game) + || !player.chooseUse(outcome, "Sacrifice another creature?", source, game) + || !cost.pay(source, game, source, source.getControllerId(), true)) { + return false; + } + int xValue = cost + .getPermanents() + .stream() + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .sum(); + game.addEffect(new SetBasePowerToughnessAllEffect( + xValue, xValue, Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMyriadPools.java b/Mage.Sets/src/mage/cards/t/TheMyriadPools.java index 8b2113b4eee..8561a812d79 100644 --- a/Mage.Sets/src/mage/cards/t/TheMyriadPools.java +++ b/Mage.Sets/src/mage/cards/t/TheMyriadPools.java @@ -168,14 +168,13 @@ class TheMyriadPoolsCopyEffect extends OneShotEffect { if (controller.choose(Outcome.Neutral, target, source, game)) { targetPermanentToCopyTo = game.getPermanent(target.getFirstTarget()); } - Card copyFromCardOnStack = game.getCard(targetPointer.getFirst(game, source)); + Card copyFromCardOnStack = game.getCard(getTargetPointer().getFirst(game, source)); Permanent newBluePrint = null; if (targetPermanentToCopyTo != null) { if (copyFromCardOnStack != null) { newBluePrint = new PermanentCard(copyFromCardOnStack, source.getControllerId(), game); newBluePrint.assignNewId(); CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBluePrint, targetPermanentToCopyTo.getId()); - copyEffect.newId(); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); game.addEffect(copyEffect, newAbility); diff --git a/Mage.Sets/src/mage/cards/t/ThePrideOfHullClade.java b/Mage.Sets/src/mage/cards/t/ThePrideOfHullClade.java new file mode 100644 index 00000000000..fee182b9037 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThePrideOfHullClade.java @@ -0,0 +1,113 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SourcePermanentToughnessValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author DominionSpy + */ +public final class ThePrideOfHullClade extends CardImpl { + + public ThePrideOfHullClade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CROCODILE); + this.subtype.add(SubType.ELK); + this.subtype.add(SubType.TURTLE); + this.power = new MageInt(2); + this.toughness = new MageInt(15); + + // This spell costs {X} less to cast, where X is the total toughness of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionSourceEffect(TotalToughnessOfControlledCreaturesValue.instance))); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // {2}{U}{U}: Until end of turn, target creature you control gets +1/+0, gains "Whenever this creature deals combat damage to a player, draw cards equal to its toughness," and can attack as though it didn't have defender. + Ability ability = new SimpleActivatedAbility( + new BoostTargetEffect(1, 0) + .setText("Until end of turn, target creature you control gets +1/+0"), + new ManaCostsImpl<>("{2}{U}{U}")) + ; + ability.addEffect( + new GainAbilityTargetEffect( + new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect + (SourcePermanentToughnessValue.getInstance()).setText("draw cards equal to its toughness"), false)) + .setText(", gains \"Whenever this creature deals combat damage to a player, draw cards equal to its toughness,\"")); + ability.addEffect( + new CanAttackAsThoughItDidntHaveDefenderTargetEffect(Duration.EndOfTurn) + .setText("and can attack as though it didn't have defender.")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private ThePrideOfHullClade(final ThePrideOfHullClade card) { + super(card); + } + + @Override + public ThePrideOfHullClade copy() { + return new ThePrideOfHullClade(this); + } +} + +enum TotalToughnessOfControlledCreaturesValue implements DynamicValue { + instance; + + @Override + public TotalToughnessOfControlledCreaturesValue copy() { + return TotalToughnessOfControlledCreaturesValue.instance; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getAllActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + sourceAbility.getControllerId(), game) + .stream() + .map(Permanent::getToughness) + .mapToInt(MageInt::getValue) + .sum(); + } + + @Override + public String getMessage() { + return "the total toughness of creatures you control"; + } + + @Override + public String toString() { + return "X"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheScarabGod.java b/Mage.Sets/src/mage/cards/t/TheScarabGod.java index 9a800fd6b5b..424d2b2dd6c 100644 --- a/Mage.Sets/src/mage/cards/t/TheScarabGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScarabGod.java @@ -1,7 +1,5 @@ - package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; @@ -22,26 +20,23 @@ import mage.abilities.hint.ValueHint; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterCreatureCard; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInGraveyard; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author spjspj */ public final class TheScarabGod extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.ZOMBIE, "Zombies you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ZOMBIE, "Zombies you control"); private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); private static final Hint hint = new ValueHint( "Number of Zombies you control", xValue @@ -60,7 +55,7 @@ public final class TheScarabGod extends CardImpl { // {2}{U}{B}: Exile target creature card from a graveyard. Create a token that's a copy of it, except it's a 4/4 black Zombie. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TheScarabGodEffect2(), new ManaCostsImpl<>("{2}{U}{B}")); - ability.addTarget(new TargetCardInGraveyard(1, 1, new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); // When The Scarab God dies, return it to its owner's hand at the beginning of the next end step. diff --git a/Mage.Sets/src/mage/cards/t/TheSeaDevils.java b/Mage.Sets/src/mage/cards/t/TheSeaDevils.java index fda64d433aa..09a21f3471d 100644 --- a/Mage.Sets/src/mage/cards/t/TheSeaDevils.java +++ b/Mage.Sets/src/mage/cards/t/TheSeaDevils.java @@ -132,11 +132,10 @@ class TheSeaDevilsTrigger extends DelayedTriggeredAbility { public String getRule() { // that triggers depends on stack order, so make each trigger unique with extra info String triggeredInfo = ""; - if (this.getEffects().get(0).getTargetPointer() != null) { - if (!this.getEffects().get(0).getTargetPointer().getData("damageAmount").isEmpty()) { - triggeredInfo += "
    Damage: " + this.getEffects().get(0).getTargetPointer().getData("damageAmount") + ""; - triggeredInfo += "
    Salamander: " + this.getEffects().get(0).getTargetPointer().getData("triggeredName") + ""; - } + String triggeredDamage = this.getEffects().get(0).getTargetPointer().getData("damageAmount"); + if (!triggeredDamage.isEmpty()) { + triggeredInfo += "
    Damage: " + this.getEffects().get(0).getTargetPointer().getData("damageAmount") + ""; + triggeredInfo += "
    Salamander: " + this.getEffects().get(0).getTargetPointer().getData("triggeredName") + ""; } return "Until end of turn, whenever a Salamander deals combat damage to a player, " + "it deals that much damage to target creature that player controls." + triggeredInfo; diff --git a/Mage.Sets/src/mage/cards/t/TheTabernacleAtPendrellVale.java b/Mage.Sets/src/mage/cards/t/TheTabernacleAtPendrellVale.java index 289dd863fc6..9ea06d18459 100644 --- a/Mage.Sets/src/mage/cards/t/TheTabernacleAtPendrellVale.java +++ b/Mage.Sets/src/mage/cards/t/TheTabernacleAtPendrellVale.java @@ -33,7 +33,7 @@ public final class TheTabernacleAtPendrellVale extends CardImpl { new InfoEffect(""), new DestroySourceEffect(), new GenericManaCost(1) ).setText("destroy this creature unless you pay {1}"), TargetController.YOU, false - ), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES) + ), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_ALL_CREATURES) )); } diff --git a/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java index aabc0feaad5..321297dda72 100644 --- a/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java +++ b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java @@ -248,8 +248,8 @@ class AddCardSubTypeEnteringTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getSpell(targetPointer.getFixedTarget(game, source).getTarget()); - MageObject target = game.getObject(targetPointer.getFixedTarget(game, source).getTarget()); + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + MageObject target = game.getObject(getTargetPointer().getFirst(game, source)); if (spell != null) { card = spell.getCard(); } diff --git a/Mage.Sets/src/mage/cards/t/TheaterOfHorrors.java b/Mage.Sets/src/mage/cards/t/TheaterOfHorrors.java index 8cd90df81c2..d17b81bd724 100644 --- a/Mage.Sets/src/mage/cards/t/TheaterOfHorrors.java +++ b/Mage.Sets/src/mage/cards/t/TheaterOfHorrors.java @@ -6,8 +6,8 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.hint.common.OpponentsLostLifeHint; import mage.cards.Card; import mage.cards.CardImpl; @@ -15,7 +15,6 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.ExileZone; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetOpponentOrPlaneswalker; import mage.util.CardUtil; import mage.watchers.common.PlayerLostLifeWatcher; @@ -32,7 +31,7 @@ public final class TheaterOfHorrors extends CardImpl { // At the beginning of your upkeep, exile the top card of your library. this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new TheaterOfHorrorsExileEffect(), + new ExileCardsFromTopOfLibraryControllerEffect(1, true), TargetController.YOU, false )); @@ -58,39 +57,6 @@ public final class TheaterOfHorrors extends CardImpl { } } -class TheaterOfHorrorsExileEffect extends OneShotEffect { - - TheaterOfHorrorsExileEffect() { - super(Outcome.Benefit); - staticText = "exile the top card of your library."; - } - - private TheaterOfHorrorsExileEffect(final TheaterOfHorrorsExileEffect effect) { - super(effect); - } - - @Override - public TheaterOfHorrorsExileEffect copy() { - return new TheaterOfHorrorsExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - Card card = player.getLibrary().getFromTop(game); - if (card == null) { - return false; - } - return player.moveCardsToExile( - card, source, game, true, CardUtil.getCardExileZoneId(game, source), - CardUtil.createObjectRealtedWindowTitle(source, game, null) - ); - } -} - class TheaterOfHorrorsCastEffect extends AsThoughEffectImpl { TheaterOfHorrorsCastEffect() { diff --git a/Mage.Sets/src/mage/cards/t/ThelonsChant.java b/Mage.Sets/src/mage/cards/t/ThelonsChant.java index f67fd1f79d5..bb9f6ab2e31 100644 --- a/Mage.Sets/src/mage/cards/t/ThelonsChant.java +++ b/Mage.Sets/src/mage/cards/t/ThelonsChant.java @@ -68,7 +68,7 @@ class ThelonsChantEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (player != null && sourcePermanent != null) { boolean paid = false; diff --git a/Mage.Sets/src/mage/cards/t/ThelonsCurse.java b/Mage.Sets/src/mage/cards/t/ThelonsCurse.java index 09a0aae89ed..294f87f41a1 100644 --- a/Mage.Sets/src/mage/cards/t/ThelonsCurse.java +++ b/Mage.Sets/src/mage/cards/t/ThelonsCurse.java @@ -84,7 +84,7 @@ class ThelonsCurseEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && sourcePermanent != null) { int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); diff --git a/Mage.Sets/src/mage/cards/t/ThievingAmalgam.java b/Mage.Sets/src/mage/cards/t/ThievingAmalgam.java index 4c38a68ec5b..cdce1ba8ef1 100644 --- a/Mage.Sets/src/mage/cards/t/ThievingAmalgam.java +++ b/Mage.Sets/src/mage/cards/t/ThievingAmalgam.java @@ -1,26 +1,24 @@ package mage.cards.t; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.cards.Card; +import mage.abilities.effects.keyword.ManifestEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.players.Player; -import java.util.Set; import java.util.UUID; /** @@ -83,31 +81,12 @@ class ThievingAmalgamManifestEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player active = game.getPlayer(game.getActivePlayerId()); - if (controller == null || active == null) { + Player targetPlayer = game.getPlayer(game.getActivePlayerId()); + if (controller == null || targetPlayer == null) { return false; } - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - Set cards = active.getLibrary().getTopCards(game, 1); - cards.stream().forEach(card -> { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED), newSource); - }); - controller.moveCards(cards, Zone.BATTLEFIELD, source, game, false, true, false, null); - cards.stream() - .map(Card::getId) - .map(game::getPermanent) - .filter(permanent -> permanent != null) - .forEach(permanent -> permanent.setManifested(true)); - return true; + + return ManifestEffect.doManifestCards(game, source, controller, targetPlayer.getLibrary().getTopCards(game, 1)); } } @@ -131,7 +110,7 @@ class ThievingAmalgamLifeLossEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(targetPointer.getFirst(game, source))); + Player player = game.getPlayer(game.getOwnerId(getTargetPointer().getFirst(game, source))); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/ThingInTheIce.java b/Mage.Sets/src/mage/cards/t/ThingInTheIce.java index 20a03323497..fea3d82c6b2 100644 --- a/Mage.Sets/src/mage/cards/t/ThingInTheIce.java +++ b/Mage.Sets/src/mage/cards/t/ThingInTheIce.java @@ -58,7 +58,7 @@ public final class ThingInTheIce extends CardImpl { effect.setText("remove an ice counter from {this}"); Ability ability = new SpellCastControllerTriggeredAbility(effect, filter, false); effect = new ConditionalOneShotEffect(new TransformSourceEffect(), new SourceHasCounterCondition(CounterType.ICE, 0, 0), - "if there are no ice counters on it, transform it"); + "Then if it has no ice counters on it, transform it"); ability.addEffect(effect); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/t/ThopterSquadron.java b/Mage.Sets/src/mage/cards/t/ThopterSquadron.java index 1a5934274b9..5aa1af6ac9c 100644 --- a/Mage.Sets/src/mage/cards/t/ThopterSquadron.java +++ b/Mage.Sets/src/mage/cards/t/ThopterSquadron.java @@ -19,10 +19,9 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.permanent.token.ThopterColorlessToken; -import mage.target.common.TargetControlledPermanent; /** * @@ -30,7 +29,7 @@ import mage.target.common.TargetControlledPermanent; */ public final class ThopterSquadron extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Thopter"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another Thopter"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java b/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java index bed81742c95..af6067f8b5d 100644 --- a/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java +++ b/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java @@ -7,8 +7,7 @@ import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,8 +16,6 @@ import java.util.UUID; */ public final class ThoroughInvestigation extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(SubType.CLUE, "a Clue"); - public ThoroughInvestigation(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); @@ -26,7 +23,7 @@ public final class ThoroughInvestigation extends CardImpl { this.addAbility(new AttacksWithCreaturesTriggeredAbility(new InvestigateEffect(), 1)); // Whenever you sacrifice a Clue, venture into the dungeon. - this.addAbility(new SacrificePermanentTriggeredAbility(new VentureIntoTheDungeonEffect(), filter)); + this.addAbility(new SacrificePermanentTriggeredAbility(new VentureIntoTheDungeonEffect(), StaticFilters.FILTER_CONTROLLED_CLUE)); } private ThoroughInvestigation(final ThoroughInvestigation card) { diff --git a/Mage.Sets/src/mage/cards/t/ThoughtDissector.java b/Mage.Sets/src/mage/cards/t/ThoughtDissector.java index 6f4295a6f6d..f385ae3303f 100644 --- a/Mage.Sets/src/mage/cards/t/ThoughtDissector.java +++ b/Mage.Sets/src/mage/cards/t/ThoughtDissector.java @@ -68,7 +68,7 @@ class ThoughtDissectorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player targetOpponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); int max = amount.calculate(game, source, this); if (targetOpponent != null && controller != null && max > 0) { int numberOfCard = 0; diff --git a/Mage.Sets/src/mage/cards/t/ThoughtPrison.java b/Mage.Sets/src/mage/cards/t/ThoughtPrison.java index b1fcd5228a9..9cbec348eff 100644 --- a/Mage.Sets/src/mage/cards/t/ThoughtPrison.java +++ b/Mage.Sets/src/mage/cards/t/ThoughtPrison.java @@ -190,7 +190,7 @@ class ThoughtPrisonDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { targetPlayer.damage(2, source.getSourceId(), source, game); return true; diff --git a/Mage.Sets/src/mage/cards/t/ThoughtboundPhantasm.java b/Mage.Sets/src/mage/cards/t/ThoughtboundPhantasm.java index 1a7a091382c..9e252c6024d 100644 --- a/Mage.Sets/src/mage/cards/t/ThoughtboundPhantasm.java +++ b/Mage.Sets/src/mage/cards/t/ThoughtboundPhantasm.java @@ -2,8 +2,8 @@ package mage.cards.t; import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SurveilTriggeredAbility; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.decorator.ConditionalAsThoughEffect; import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; @@ -16,8 +16,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; /** * @@ -36,7 +34,7 @@ public final class ThoughtboundPhantasm extends CardImpl { this.addAbility(DefenderAbility.getInstance()); // Whenever you surveil, put a +1/+1 counter on Thoughtbound Phantasm. - this.addAbility(new ThoughtboundPhantasmTriggeredAbility()); + this.addAbility(new SurveilTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); // As long as Thoughtbound Phantasm has three or more +1/+1 counters on it, it can attack as though it didn't have defender. this.addAbility(new SimpleStaticAbility( @@ -61,36 +59,3 @@ public final class ThoughtboundPhantasm extends CardImpl { return new ThoughtboundPhantasm(this); } } - -class ThoughtboundPhantasmTriggeredAbility extends TriggeredAbilityImpl { - - public ThoughtboundPhantasmTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect( - CounterType.P1P1.createInstance() - ), false); - } - - private ThoughtboundPhantasmTriggeredAbility(final ThoughtboundPhantasmTriggeredAbility ability) { - super(ability); - } - - @Override - public ThoughtboundPhantasmTriggeredAbility copy() { - return new ThoughtboundPhantasmTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SURVEILED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.getControllerId()); - } - - @Override - public String getRule() { - return "Whenever you surveil, put a +1/+1 counter on {this}."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/ThrabenHeretic.java b/Mage.Sets/src/mage/cards/t/ThrabenHeretic.java index 4c172e80aaf..eb2fb56b274 100644 --- a/Mage.Sets/src/mage/cards/t/ThrabenHeretic.java +++ b/Mage.Sets/src/mage/cards/t/ThrabenHeretic.java @@ -12,6 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -31,7 +32,7 @@ public final class ThrabenHeretic extends CardImpl { // {tap}: Exile target creature card from a graveyard. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), new TapSourceCost()); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/ThranPortal.java b/Mage.Sets/src/mage/cards/t/ThranPortal.java index 68c74ea50e9..e901ca3a132 100644 --- a/Mage.Sets/src/mage/cards/t/ThranPortal.java +++ b/Mage.Sets/src/mage/cards/t/ThranPortal.java @@ -94,6 +94,11 @@ class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl { public void init(Ability source, Game game) { super.init(source, game); SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + discard(); + return; + } + switch (choice) { case FOREST: dependencyTypes.add(DependencyType.BecomeForest); diff --git a/Mage.Sets/src/mage/cards/t/ThranTurbine.java b/Mage.Sets/src/mage/cards/t/ThranTurbine.java index 0c1e36b78bf..b0fcdbd7040 100644 --- a/Mage.Sets/src/mage/cards/t/ThranTurbine.java +++ b/Mage.Sets/src/mage/cards/t/ThranTurbine.java @@ -66,7 +66,7 @@ class ThranTurbineEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { new AddConditionalColorlessManaEffect(2, new ThranTurbineManaBuilder()).apply(game, source); diff --git a/Mage.Sets/src/mage/cards/t/ThrashOfRaptors.java b/Mage.Sets/src/mage/cards/t/ThrashOfRaptors.java index ae96a235cf3..7f17f17e714 100644 --- a/Mage.Sets/src/mage/cards/t/ThrashOfRaptors.java +++ b/Mage.Sets/src/mage/cards/t/ThrashOfRaptors.java @@ -16,7 +16,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; /** @@ -25,7 +25,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; */ public final class ThrashOfRaptors extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another Dinosaur"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another Dinosaur"); static { filter.add(SubType.DINOSAUR.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/t/ThrummingStone.java b/Mage.Sets/src/mage/cards/t/ThrummingStone.java index c7586c6a57d..23e0ce164d6 100644 --- a/Mage.Sets/src/mage/cards/t/ThrummingStone.java +++ b/Mage.Sets/src/mage/cards/t/ThrummingStone.java @@ -21,8 +21,10 @@ public final class ThrummingStone extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // Spells you cast have ripple 4. - this.addAbility(new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new RippleAbility(4), StaticFilters.FILTER_CARD) - .setText("spells you cast have ripple 4"))); + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new RippleAbility(4), StaticFilters.FILTER_CARD_NON_LAND) + .setText("Spells you cast have ripple 4. (Whenever you cast a spell, you may reveal the top four cards " + + "of your library. You may cast spells with the same name as that spell from among the revealed " + + "cards without paying their mana costs. Put the rest on the bottom of your library.)"))); } private ThrummingStone(final ThrummingStone card) { diff --git a/Mage.Sets/src/mage/cards/t/ThunderfootBaloth.java b/Mage.Sets/src/mage/cards/t/ThunderfootBaloth.java index 0afc08566ff..e14ba8b37cd 100644 --- a/Mage.Sets/src/mage/cards/t/ThunderfootBaloth.java +++ b/Mage.Sets/src/mage/cards/t/ThunderfootBaloth.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -37,7 +37,7 @@ public final class ThunderfootBaloth extends CardImpl { Effect effect = new BoostControlledEffect(2, 2, Duration.WhileOnBattlefield, true); effect.setText("and other creatures you control get +2/+2"); effects.add(effect); - effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield, new FilterControlledCreaturePermanent(), true); + effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURES, true); effect.setText("and have trample"); effects.add(effect); this.addAbility(new LieutenantAbility(effects)); diff --git a/Mage.Sets/src/mage/cards/t/TibaltTheFiendBlooded.java b/Mage.Sets/src/mage/cards/t/TibaltTheFiendBlooded.java index 54c41087386..bcbb71788cf 100644 --- a/Mage.Sets/src/mage/cards/t/TibaltTheFiendBlooded.java +++ b/Mage.Sets/src/mage/cards/t/TibaltTheFiendBlooded.java @@ -5,16 +5,12 @@ import mage.abilities.LoyaltyAbility; import mage.abilities.dynamicvalue.common.CardsInTargetHandCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.UntapAllEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; -import mage.abilities.effects.common.continuous.GainControlAllEffect; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; import mage.abilities.effects.common.discard.DiscardControllerEffect; -import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.StaticFilters; @@ -47,14 +43,7 @@ public final class TibaltTheFiendBlooded extends CardImpl { this.addAbility(ability); // -6: Gain control of all creatures until end of turn. Untap them. They gain haste until end of turn. - ability = new LoyaltyAbility(new GainControlAllEffect(Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES), -6); - ability.addEffect(new UntapAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES).setText("untap them")); - ability.addEffect(new GainAbilityAllEffect( - HasteAbility.getInstance(), - Duration.EndOfTurn, - StaticFilters.FILTER_PERMANENT_CREATURES, - "they gain haste until end of turn" - )); + ability = new LoyaltyAbility(new GainControlAllUntapGainHasteEffect(StaticFilters.FILTER_PERMANENT_CREATURES), -6); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TibaltsTrickery.java b/Mage.Sets/src/mage/cards/t/TibaltsTrickery.java index 0d664316eef..39075607b9f 100644 --- a/Mage.Sets/src/mage/cards/t/TibaltsTrickery.java +++ b/Mage.Sets/src/mage/cards/t/TibaltsTrickery.java @@ -71,7 +71,7 @@ class TibaltsTrickeryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { String spellName = spell.getName(); Player controller = game.getPlayer(spell.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/t/TideShaper.java b/Mage.Sets/src/mage/cards/t/TideShaper.java index 53c7db5bb56..5d49565b16b 100644 --- a/Mage.Sets/src/mage/cards/t/TideShaper.java +++ b/Mage.Sets/src/mage/cards/t/TideShaper.java @@ -92,11 +92,10 @@ class TideShaperEffect extends BecomesBasicLandTargetEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); if (source.getSourcePermanentIfItStillExists(game) == null) { discard(); - return; } - super.init(source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TimotharBaronOfBats.java b/Mage.Sets/src/mage/cards/t/TimotharBaronOfBats.java index c981650054d..a75e13e19eb 100644 --- a/Mage.Sets/src/mage/cards/t/TimotharBaronOfBats.java +++ b/Mage.Sets/src/mage/cards/t/TimotharBaronOfBats.java @@ -89,7 +89,7 @@ class TimotharBaronOfBatsCreateBatEffect extends OneShotEffect { } // Check vampire card still exists and is still in the graveyard - Card vampireCard = game.getCard(targetPointer.getFirst(game, source)); + Card vampireCard = game.getCard(getTargetPointer().getFirst(game, source)); if (vampireCard == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TinStreetCadet.java b/Mage.Sets/src/mage/cards/t/TinStreetCadet.java new file mode 100644 index 00000000000..b73d14e567a --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TinStreetCadet.java @@ -0,0 +1,39 @@ + +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.BecomesBlockedSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.GoblinToken; + +/** + * + * @author tiera3 - modified from GoblinInstigator + */ +public final class TinStreetCadet extends CardImpl { + + public TinStreetCadet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.GOBLIN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever Tin Street Cadet becomes blocked, create a 1/1 red Goblin creature token. + this.addAbility(new BecomesBlockedSourceTriggeredAbility(new CreateTokenEffect(new GoblinToken()), false)); + } + + private TinStreetCadet(final TinStreetCadet card) { + super(card); + } + + @Override + public TinStreetCadet copy() { + return new TinStreetCadet(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TinStreetGossip.java b/Mage.Sets/src/mage/cards/t/TinStreetGossip.java new file mode 100644 index 00000000000..126a63502f0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TinStreetGossip.java @@ -0,0 +1,87 @@ +package mage.cards.t; + +import java.util.UUID; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.TurnFaceUpAbility; +import mage.abilities.condition.Condition; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.constants.SubType; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.game.stack.Spell; + +/** + * + * @author DominionSpy + */ +public final class TinStreetGossip extends CardImpl { + + public TinStreetGossip(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.subtype.add(SubType.VIASHINO); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // {T}: Add {R}{G}. Spend this mana only to cast face-down spells or to turn creatures face up. + this.addAbility(new ConditionalColoredManaAbility( + new Mana(0, 0, 0, 1, 1, 0, 0, 0), + new TinStreetGossipManaBuilder())); + } + + private TinStreetGossip(final TinStreetGossip card) { + super(card); + } + + @Override + public TinStreetGossip copy() { + return new TinStreetGossip(this); + } +} + +class TinStreetGossipManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new TinStreetGossipConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast face-down spells or to turn creatures face up."; + } +} + +class TinStreetGossipConditionalMana extends ConditionalMana { + + TinStreetGossipConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast face-down spells or to turn creatures face up."; + addCondition(new TinStreetGossipManaCondition()); + } +} + +class TinStreetGossipManaCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + MageObject object = game.getObject(source); + if (object instanceof Spell) { + return ((Spell) object).isFaceDown(game); + } + return source instanceof TurnFaceUpAbility; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TirelessTracker.java b/Mage.Sets/src/mage/cards/t/TirelessTracker.java index 2f68429c1c0..516266d4881 100644 --- a/Mage.Sets/src/mage/cards/t/TirelessTracker.java +++ b/Mage.Sets/src/mage/cards/t/TirelessTracker.java @@ -10,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -19,8 +19,6 @@ import java.util.UUID; */ public final class TirelessTracker extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CLUE, "a Clue"); - public TirelessTracker(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.HUMAN); @@ -33,7 +31,7 @@ public final class TirelessTracker extends CardImpl { // Whenever you sacrifice a Clue, put a +1/+1 counter on Tireless Tracker. this.addAbility(new SacrificePermanentTriggeredAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), StaticFilters.FILTER_CONTROLLED_CLUE )); } diff --git a/Mage.Sets/src/mage/cards/t/TitanicPelagosaur.java b/Mage.Sets/src/mage/cards/t/TitanicPelagosaur.java deleted file mode 100644 index 8d56db57f7b..00000000000 --- a/Mage.Sets/src/mage/cards/t/TitanicPelagosaur.java +++ /dev/null @@ -1,32 +0,0 @@ -package mage.cards.t; - -import mage.MageInt; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author JayDi85 - */ -public final class TitanicPelagosaur extends CardImpl { - - public TitanicPelagosaur(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); - this.subtype.add(SubType.DINOSAUR); - - this.power = new MageInt(4); - this.toughness = new MageInt(6); - } - - private TitanicPelagosaur(final TitanicPelagosaur card) { - super(card); - } - - @Override - public TitanicPelagosaur copy() { - return new TitanicPelagosaur(this); - } -} diff --git a/Mage.Sets/src/mage/cards/t/ToArms.java b/Mage.Sets/src/mage/cards/t/ToArms.java index c7d6ec43556..5a7d7e996c0 100644 --- a/Mage.Sets/src/mage/cards/t/ToArms.java +++ b/Mage.Sets/src/mage/cards/t/ToArms.java @@ -8,7 +8,7 @@ import mage.abilities.effects.common.UntapAllControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -23,7 +23,7 @@ public final class ToArms extends CardImpl { // Untap all creatures you control. - Effect effect = new UntapAllControllerEffect(new FilterControlledCreaturePermanent(), rule); + Effect effect = new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, rule); this.getSpellAbility().addEffect(effect); // Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
    ")); diff --git a/Mage.Sets/src/mage/cards/t/TolarianEntrancer.java b/Mage.Sets/src/mage/cards/t/TolarianEntrancer.java index 3e866c8ff5f..3f38f6ef320 100644 --- a/Mage.Sets/src/mage/cards/t/TolarianEntrancer.java +++ b/Mage.Sets/src/mage/cards/t/TolarianEntrancer.java @@ -1,4 +1,3 @@ - package mage.cards.t; import java.util.UUID; @@ -14,7 +13,6 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; /** * @@ -42,34 +40,35 @@ public final class TolarianEntrancer extends CardImpl { return new TolarianEntrancer(this); } - class TolarianEntrancerDelayedTriggeredAbility extends DelayedTriggeredAbility { +} - public TolarianEntrancerDelayedTriggeredAbility() { - super(new GainControlTargetEffect(Duration.EndOfGame)); - } +class TolarianEntrancerDelayedTriggeredAbility extends DelayedTriggeredAbility { - private TolarianEntrancerDelayedTriggeredAbility(final TolarianEntrancerDelayedTriggeredAbility ability) { - super(ability); - } + TolarianEntrancerDelayedTriggeredAbility() { + super(new GainControlTargetEffect(Duration.EndOfGame)); + } - @Override - public TolarianEntrancerDelayedTriggeredAbility copy() { - return new TolarianEntrancerDelayedTriggeredAbility(this); - } + private TolarianEntrancerDelayedTriggeredAbility(final TolarianEntrancerDelayedTriggeredAbility ability) { + super(ability); + } - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST; - } + @Override + public TolarianEntrancerDelayedTriggeredAbility copy() { + return new TolarianEntrancerDelayedTriggeredAbility(this); + } - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return true; - } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST; + } - @Override - public String getRule() { - return "gain control of that creature at end of combat"; - } + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return true; + } + + @Override + public String getRule() { + return "gain control of that creature at end of combat"; } } diff --git a/Mage.Sets/src/mage/cards/t/TolsimirMidnightsLight.java b/Mage.Sets/src/mage/cards/t/TolsimirMidnightsLight.java new file mode 100644 index 00000000000..1a5ed067aa5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TolsimirMidnightsLight.java @@ -0,0 +1,138 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.RequirementEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.Duration; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TolsimirMidnightsLightToken; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; + +/** + * + * @author DominionSpy + */ +public final class TolsimirMidnightsLight extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.WOLF, "Wolf you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public TolsimirMidnightsLight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Tolsimir, Midnight's Light enters the battlefield, create Voja Fenstalker, a legendary 5/5 green and white Wolf creature token with trample. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TolsimirMidnightsLightToken()))); + + // Whenever a Wolf you control attacks, if Tolsimir attacked this combat, target creature an opponent controls blocks that Wolf this combat if able. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new AttacksAllTriggeredAbility(new TolsimirMidnightsLightEffect(), false, filter, SetTargetPointer.PERMANENT, false), + TolsimirMidnightsLightCondition.instance, + "Whenever a Wolf you control attacks, if {this} attacked this combat, target creature an opponent controls blocks that Wolf this combat if able."); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability, new AttackedOrBlockedThisCombatWatcher()); + } + + private TolsimirMidnightsLight(final TolsimirMidnightsLight card) { + super(card); + } + + @Override + public TolsimirMidnightsLight copy() { + return new TolsimirMidnightsLight(this); + } +} + +enum TolsimirMidnightsLightCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (sourcePermanent == null) { + return false; + } + AttackedOrBlockedThisCombatWatcher watcher = game.getState().getWatcher(AttackedOrBlockedThisCombatWatcher.class); + if (watcher == null) { + return false; + } + for (MageObjectReference mor : watcher.getAttackedThisTurnCreatures()) { + if (mor.refersTo(sourcePermanent, game)) { + return true; + } + } + return false; + } +} + +class TolsimirMidnightsLightEffect extends RequirementEffect { + + TolsimirMidnightsLightEffect() { + super(Duration.EndOfCombat); + } + + private TolsimirMidnightsLightEffect(final TolsimirMidnightsLightEffect effect) { + super(effect); + } + + @Override + public TolsimirMidnightsLightEffect copy() { + return new TolsimirMidnightsLightEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + Permanent attacker = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (attacker == null) { + discard(); + return false; + } + return permanent != null && + permanent.getId().equals(source.getFirstTarget()) && + permanent.canBlock(getTargetPointer().getFirst(game, source), game); + } + + @Override + public boolean mustAttack(Game game) { + return false; + } + + @Override + public boolean mustBlock(Game game) { + return false; + } + + @Override + public UUID mustBlockAttacker(Ability source, Game game) { + return getTargetPointer().getFirst(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Tombfire.java b/Mage.Sets/src/mage/cards/t/Tombfire.java index 0c4d48b26d2..d87866fb56a 100644 --- a/Mage.Sets/src/mage/cards/t/Tombfire.java +++ b/Mage.Sets/src/mage/cards/t/Tombfire.java @@ -61,7 +61,7 @@ class TombfireEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer != null) { for (Card card : targetPlayer.getGraveyard().getCards(filter, game)) { card.moveToExile(null, "", source, game); diff --git a/Mage.Sets/src/mage/cards/t/TomikWielderOfLaw.java b/Mage.Sets/src/mage/cards/t/TomikWielderOfLaw.java new file mode 100644 index 00000000000..b33d524311c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TomikWielderOfLaw.java @@ -0,0 +1,120 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author DominionSpy + */ +public final class TomikWielderOfLaw extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPlaneswalkerPermanent("planeswalkers"); + private static final Hint hint = new ValueHint("planeswalkers you control", new PermanentsOnBattlefieldCount(filter)); + + public TomikWielderOfLaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Affinity for planeswalkers + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)) + .addHint(hint)); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever an opponent attacks with creatures, if two or more of those creatures are attacking you and/or planeswalkers you control, that opponent loses 3 life and you draw a card. + this.addAbility(new TomikWielderOfLawTriggeredAbility()); + } + + private TomikWielderOfLaw(final TomikWielderOfLaw card) { + super(card); + } + + @Override + public TomikWielderOfLaw copy() { + return new TomikWielderOfLaw(this); + } +} + +class TomikWielderOfLawTriggeredAbility extends TriggeredAbilityImpl { + + TomikWielderOfLawTriggeredAbility() { + super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(3)); + addEffect(new DrawCardSourceControllerEffect(1)); + } + + private TomikWielderOfLawTriggeredAbility(final TomikWielderOfLawTriggeredAbility ability) { + super(ability); + } + + @Override + public TomikWielderOfLawTriggeredAbility copy() { + return new TomikWielderOfLawTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Player player = game.getPlayer(getControllerId()); + if (player != null + && player.hasOpponent(game.getActivePlayerId(), game)) { + getEffects().setTargetPointer(new FixedTarget(game.getCombat().getAttackingPlayerId())); + return true; + } + return false; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + return game + .getCombat() + .getAttackers() + .stream() + .map(uuid -> game.getCombat().getDefendingPlayerId(uuid, game)) + .filter(getControllerId()::equals) + .count() >= 2; + } + + @Override + public String getRule() { + return "Whenever an opponent attacks with creatures, " + + "if two or more of those creatures are attacking you " + + "and/or planeswalkers you control, that opponent loses 3 life " + + "and you draw a card."; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TooGreedilyTooDeep.java b/Mage.Sets/src/mage/cards/t/TooGreedilyTooDeep.java index e335556962b..c26f333a848 100644 --- a/Mage.Sets/src/mage/cards/t/TooGreedilyTooDeep.java +++ b/Mage.Sets/src/mage/cards/t/TooGreedilyTooDeep.java @@ -23,14 +23,12 @@ import java.util.UUID; */ public final class TooGreedilyTooDeep extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public TooGreedilyTooDeep(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{B}{R}"); // Put target creature card from a graveyard onto the battlefield under your control. That creature deals damage equal to its power to each other creature. this.getSpellAbility().addEffect(new TooGreedilyTooDeepEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); } private TooGreedilyTooDeep(final TooGreedilyTooDeep card) { diff --git a/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java b/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java index 4c7e4e26ed8..41621310d63 100644 --- a/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java +++ b/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java @@ -72,7 +72,7 @@ class TormentOfScarabsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player enchantedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player enchantedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (enchantedPlayer == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TormentorsHelm.java b/Mage.Sets/src/mage/cards/t/TormentorsHelm.java index a5d37895ecf..8dc290b5255 100644 --- a/Mage.Sets/src/mage/cards/t/TormentorsHelm.java +++ b/Mage.Sets/src/mage/cards/t/TormentorsHelm.java @@ -114,7 +114,7 @@ class TormentorsHelmEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(1, creatureId, source, game); return true; diff --git a/Mage.Sets/src/mage/cards/t/TorrentOfLava.java b/Mage.Sets/src/mage/cards/t/TorrentOfLava.java new file mode 100644 index 00000000000..4ad328c94c8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TorrentOfLava.java @@ -0,0 +1,134 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.PreventDamageToSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.util.GameLog; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class TorrentOfLava extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature without flying"); + + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public TorrentOfLava(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}"); + + // Torrent of Lava deals X damage to each creature without flying. + this.getSpellAbility().addEffect(new DamageAllEffect(ManacostVariableValue.REGULAR, filter)); + + // As long as Torrent of Lava is on the stack, each creature has + // "{tap}: Prevent the next 1 damage that would be dealt to this creature by Torrent of Lava this turn." + this.addAbility(new SimpleStaticAbility(Zone.STACK, new TorrentOfLavaGainAbilityEffect())); + } + + private TorrentOfLava(final TorrentOfLava card) { + super(card); + } + + @Override + public TorrentOfLava copy() { + return new TorrentOfLava(this); + } +} + +class TorrentOfLavaGainAbilityEffect extends GainAbilityAllEffect { + + TorrentOfLavaGainAbilityEffect() { + super(new SimpleActivatedAbility( + new TorrentOfLavaPreventionEffect(null, 0), new TapSourceCost()), + Duration.Custom, + StaticFilters.FILTER_PERMANENT_CREATURES); + this.staticText = "As long as {this} is on the stack, " + + "each creature has \"{T}: Prevent the next 1 damage that would be dealt to this creature by {this} this turn.\""; + } + + private TorrentOfLavaGainAbilityEffect(final TorrentOfLavaGainAbilityEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell == null) { + return false; + } + + Effect effect = new TorrentOfLavaPreventionEffect(spell.getId(), spell.getZoneChangeCounter(game)); + // Display the id of the spell on the stack, not the card id + String idName = spell.getName() + " [" + spell.getId().toString().substring(0, 3) + "]"; + effect.setText("Prevent the next 1 damage that would be dealt to {this} by " + + GameLog.getColoredObjectIdNameForTooltip(spell.getColor(game), idName) + " this turn"); + + ability = new SimpleActivatedAbility(effect, new TapSourceCost()); + return super.apply(game, source); + } + + @Override + public TorrentOfLavaGainAbilityEffect copy() { + return new TorrentOfLavaGainAbilityEffect(this); + } +} + +class TorrentOfLavaPreventionEffect extends PreventDamageToSourceEffect { + + private final UUID preventDamageFromId; + private final int zoneChangeCounter; + + TorrentOfLavaPreventionEffect(UUID preventDamageFromId, int zoneChangeCounter) { + super(Duration.EndOfTurn, 1); + this.preventDamageFromId = preventDamageFromId; + this.zoneChangeCounter = zoneChangeCounter; + } + + private TorrentOfLavaPreventionEffect(final TorrentOfLavaPreventionEffect effect) { + super(effect); + this.preventDamageFromId = effect.preventDamageFromId; + this.zoneChangeCounter = effect.zoneChangeCounter; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!super.applies(event, source, game) || preventDamageFromId == null) { + return false; + } + + Spell spell = game.getStack().getSpell(event.getSourceId()); + if (spell == null) { + return false; + } + + return spell.getId().equals(preventDamageFromId) && spell.getZoneChangeCounter(game) == zoneChangeCounter; + } + + @Override + public TorrentOfLavaPreventionEffect copy() { + return new TorrentOfLavaPreventionEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TourachsChant.java b/Mage.Sets/src/mage/cards/t/TourachsChant.java index d1b67f4ec56..9f7fe6f4b25 100644 --- a/Mage.Sets/src/mage/cards/t/TourachsChant.java +++ b/Mage.Sets/src/mage/cards/t/TourachsChant.java @@ -68,7 +68,7 @@ class TourachsChantEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (player != null && sourcePermanent != null) { boolean paid = false; diff --git a/Mage.Sets/src/mage/cards/t/TourachsGate.java b/Mage.Sets/src/mage/cards/t/TourachsGate.java index 1978b0f5519..25230a8a1e6 100644 --- a/Mage.Sets/src/mage/cards/t/TourachsGate.java +++ b/Mage.Sets/src/mage/cards/t/TourachsGate.java @@ -1,4 +1,3 @@ - package mage.cards.t; import java.util.UUID; @@ -26,14 +25,12 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.AttackingPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledPermanent; /** @@ -58,10 +55,7 @@ public final class TourachsGate extends CardImpl { filterAttackingCreatures.add(TargetController.YOU.getControllerPredicate()); } - private static final FilterControlledCreaturePermanent filterThrull = new FilterControlledCreaturePermanent("a Thrull"); - static { - filterThrull.add(SubType.THRULL.getPredicate()); - } + private static final FilterControlledPermanent filterThrull = new FilterControlledPermanent(SubType.THRULL, "a Thrull"); public TourachsGate(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}"); diff --git a/Mage.Sets/src/mage/cards/t/TrailOfMystery.java b/Mage.Sets/src/mage/cards/t/TrailOfMystery.java index 84a751c47b2..2aaa9a498a7 100644 --- a/Mage.Sets/src/mage/cards/t/TrailOfMystery.java +++ b/Mage.Sets/src/mage/cards/t/TrailOfMystery.java @@ -1,4 +1,3 @@ - package mage.cards.t; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; @@ -52,7 +51,7 @@ public final class TrailOfMystery extends CardImpl { class TrailOfMysteryTriggeredAbility extends TurnedFaceUpAllTriggeredAbility { - public TrailOfMysteryTriggeredAbility() { + TrailOfMysteryTriggeredAbility() { super(new BoostTargetEffect(2, 2, Duration.EndOfTurn), new FilterControlledCreaturePermanent(), true); } diff --git a/Mage.Sets/src/mage/cards/t/TrainingGrounds.java b/Mage.Sets/src/mage/cards/t/TrainingGrounds.java index 7abe0542577..dd53b87af84 100644 --- a/Mage.Sets/src/mage/cards/t/TrainingGrounds.java +++ b/Mage.Sets/src/mage/cards/t/TrainingGrounds.java @@ -6,14 +6,12 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.choices.ChoiceImpl; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import java.util.LinkedHashSet; -import java.util.Set; + import java.util.UUID; /** @@ -64,26 +62,8 @@ class TrainingGroundsEffect extends CostModificationEffectImpl { if (reduceMax <= 0) { return true; } - ChoiceImpl choice = new ChoiceImpl(true); - Set set = new LinkedHashSet<>(); - - int reduce; - if (game.inCheckPlayableState()) { - reduce = reduceMax; - } else { - for (int i = 0; i <= reduceMax; i++) { - set.add(String.valueOf(i)); - } - choice.setChoices(set); - choice.setMessage("Reduce ability cost"); - if (!controller.choose(Outcome.Benefit, choice, game)) { - return false; - } - reduce = Integer.parseInt(choice.getChoice()); - } - CardUtil.reduceCost(abilityToModify, reduce); + CardUtil.reduceCost(abilityToModify, reduceMax); return true; - } @Override diff --git a/Mage.Sets/src/mage/cards/t/TraitorsRoar.java b/Mage.Sets/src/mage/cards/t/TraitorsRoar.java index dbd922f4115..7b300ac518c 100644 --- a/Mage.Sets/src/mage/cards/t/TraitorsRoar.java +++ b/Mage.Sets/src/mage/cards/t/TraitorsRoar.java @@ -68,7 +68,7 @@ class TraitorsRoarEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean applied = false; - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { applied = targetCreature.tap(source, game); Player controller = game.getPlayer(targetCreature.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/t/TrawlerDrake.java b/Mage.Sets/src/mage/cards/t/TrawlerDrake.java index 5a292648eb2..c343e7f7774 100644 --- a/Mage.Sets/src/mage/cards/t/TrawlerDrake.java +++ b/Mage.Sets/src/mage/cards/t/TrawlerDrake.java @@ -44,7 +44,8 @@ public final class TrawlerDrake extends CardImpl { )); // Trawler Drake gets +1/+1 for each oil counter on it. - this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield) + .setText("{this} gets +1/+1 for each oil counter on it"))); // Whenever you cast a noncreature spell, put an oil counter on Trawler Drake. this.addAbility(new SpellCastControllerTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/t/TreacherousUrge.java b/Mage.Sets/src/mage/cards/t/TreacherousUrge.java index 9d84a610fb3..dcb297c2e0c 100644 --- a/Mage.Sets/src/mage/cards/t/TreacherousUrge.java +++ b/Mage.Sets/src/mage/cards/t/TreacherousUrge.java @@ -67,7 +67,7 @@ class TreacherousUrgeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (opponent != null && sourceObject != null) { opponent.revealCards(sourceObject.getName(), opponent.getHand(), game); diff --git a/Mage.Sets/src/mage/cards/t/TreasureNabber.java b/Mage.Sets/src/mage/cards/t/TreasureNabber.java index 435627c416d..5a051a50980 100644 --- a/Mage.Sets/src/mage/cards/t/TreasureNabber.java +++ b/Mage.Sets/src/mage/cards/t/TreasureNabber.java @@ -89,8 +89,6 @@ class TreasureNabberAbility extends TriggeredAbilityImpl { class TreasureNabberEffect extends ContinuousEffectImpl { - protected FixedTargets fixedTargets; - TreasureNabberEffect() { super(Duration.UntilEndOfYourNextTurn, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); this.staticText = "gain control of that artifact until the end of your next turn"; @@ -98,7 +96,6 @@ class TreasureNabberEffect extends ContinuousEffectImpl { private TreasureNabberEffect(final TreasureNabberEffect effect) { super(effect); - this.fixedTargets = effect.fixedTargets; } @Override @@ -116,8 +113,4 @@ class TreasureNabberEffect extends ContinuousEffectImpl { } return false; } - - public void setTargets(List targetedPermanents, Game game) { - this.fixedTargets = new FixedTargets(targetedPermanents, game); - } } diff --git a/Mage.Sets/src/mage/cards/t/TribuneOfRot.java b/Mage.Sets/src/mage/cards/t/TribuneOfRot.java new file mode 100644 index 00000000000..f68169c59ba --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TribuneOfRot.java @@ -0,0 +1,80 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.SaprolingToken; +import mage.game.permanent.token.Token; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author BenGiese22 + */ +public final class TribuneOfRot extends CardImpl { + + public TribuneOfRot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B/G}{B/G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.SHAMAN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever Tribune of Rot attacks, mill two cards. For each creature card milled this way, create a 1/1 green Saproling creature token. + this.addAbility(new AttacksTriggeredAbility(new TribuneOfRotEffect())); + } + + private TribuneOfRot(final TribuneOfRot card) { + super(card); + } + + @Override + public TribuneOfRot copy() { + return new TribuneOfRot(this); + } +} + +class TribuneOfRotEffect extends OneShotEffect { + + private static final Token token = new SaprolingToken(); + + TribuneOfRotEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "mill two cards. For each creature card milled this way, create a 1/1 green Saproling creature token"; + } + + private TribuneOfRotEffect(final TribuneOfRotEffect effect) { + super(effect); + } + + @Override + public TribuneOfRotEffect copy() { + return new TribuneOfRotEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + int numOfCreatureCardsMilled = player + .millCards(2, source, game) + .count(StaticFilters.FILTER_CARD_CREATURE, game); + if (numOfCreatureCardsMilled > 0) { + game.getState().processAction(game); + token.putOntoBattlefield(numOfCreatureCardsMilled, game, source, source.getControllerId(), false, false); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TroubleInPairs.java b/Mage.Sets/src/mage/cards/t/TroubleInPairs.java new file mode 100644 index 00000000000..dfffc3f5fd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TroubleInPairs.java @@ -0,0 +1,98 @@ +package mage.cards.t; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SkipExtraTurnsAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.watchers.common.CardsDrawnThisTurnWatcher; +import mage.watchers.common.CastSpellLastTurnWatcher; + +import java.util.*; + +/** + * @author PurpleCrowbar + */ +public final class TroubleInPairs extends CardImpl { + + public TroubleInPairs(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); + + // If an opponent would begin an extra turn, that player skips that turn instead. + this.addAbility(new SkipExtraTurnsAbility(true)); + + // Whenever an opponent attacks you with two or more creatures, draws their second card each turn, or casts their second spell each turn, you draw a card. + this.addAbility(new TroubleInPairsTriggeredAbility()); + } + + private TroubleInPairs(final TroubleInPairs card) { + super(card); + } + + @Override + public TroubleInPairs copy() { + return new TroubleInPairs(this); + } +} + +class TroubleInPairsTriggeredAbility extends TriggeredAbilityImpl { + + TroubleInPairsTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + } + + private TroubleInPairsTriggeredAbility(final TroubleInPairsTriggeredAbility ability) { + super(ability); + } + + @Override + public TroubleInPairsTriggeredAbility copy() { + return new TroubleInPairsTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS + || event.getType() == GameEvent.EventType.DREW_CARD + || event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Player controller = game.getPlayer(getControllerId()); + if (controller == null || !game.getOpponents(getControllerId()).contains(event.getPlayerId())) { + return false; + } + switch (event.getType()) { + // Whenever an opponent attacks you with two or more creatures + case DECLARED_ATTACKERS: + return game + .getCombat() + .getAttackers() + .stream() + .map(uuid -> game.getCombat().getDefendingPlayerId(uuid, game)) + .filter(getControllerId()::equals) + .count() >= 2; + // Whenever an opponent draws their second card each turn + case DREW_CARD: + CardsDrawnThisTurnWatcher drawWatcher = game.getState().getWatcher(CardsDrawnThisTurnWatcher.class); + return drawWatcher != null && drawWatcher.getCardsDrawnThisTurn(event.getPlayerId()) == 2; + // Whenever an opponent casts their second spell each turn + case SPELL_CAST: + CastSpellLastTurnWatcher spellWatcher = game.getState().getWatcher(CastSpellLastTurnWatcher.class); + return spellWatcher != null && spellWatcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 2; + } + + return false; + } + + @Override + public String getRule() { + return "Whenever an opponent attacks you with two or more creatures, draws their second " + + "card each turn, or casts their second spell each turn, you draw a card."; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TsabosDecree.java b/Mage.Sets/src/mage/cards/t/TsabosDecree.java index a6d630ea7d6..8fccea33b27 100644 --- a/Mage.Sets/src/mage/cards/t/TsabosDecree.java +++ b/Mage.Sets/src/mage/cards/t/TsabosDecree.java @@ -59,7 +59,7 @@ class TsabosDecreeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = game.getObject(source); if (player == null) { return false; diff --git a/Mage.Sets/src/mage/cards/t/TunnelTipster.java b/Mage.Sets/src/mage/cards/t/TunnelTipster.java new file mode 100644 index 00000000000..fde225d3703 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TunnelTipster.java @@ -0,0 +1,108 @@ +package mage.cards.t; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.mana.GreenManaAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.watchers.Watcher; + +/** + * + * @author DominionSpy + */ +public final class TunnelTipster extends CardImpl { + + public TunnelTipster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.MOLE); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // At the beginning of your end step, if a face-down creature entered the battlefield under your control this turn, put a +1/+1 counter on Tunnel Tipster. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), TargetController.YOU, + TunnelTipsterCondition.instance, false); + ability.addHint(new ConditionHint(TunnelTipsterCondition.instance, + "a face-down creature entered the battlefield under your control")); + this.addAbility(ability, new TunnelTipsterWatcher()); + // {T}: Add {G}. + this.addAbility(new GreenManaAbility()); + } + + private TunnelTipster(final TunnelTipster card) { + super(card); + } + + @Override + public TunnelTipster copy() { + return new TunnelTipster(this); + } +} + +class TunnelTipsterWatcher extends Watcher { + + private final Set players = new HashSet<>(); + + TunnelTipsterWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() == Zone.BATTLEFIELD && + zEvent.getTarget().isCreature(game) && + zEvent.getTarget().isFaceDown(game)) { + players.add(zEvent.getTarget().getControllerId()); + } + } + } + + @Override + public void reset() { + super.reset(); + players.clear(); + } + + public static boolean faceDownCreatureEnteredForPlayer(UUID playerId, Game game) { + return game + .getState() + .getWatcher(TunnelTipsterWatcher.class) + .players + .contains(playerId); + } +} + +enum TunnelTipsterCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return TunnelTipsterWatcher.faceDownCreatureEnteredForPlayer(source.getControllerId(), game); + } + + @Override + public String toString() { + return "a face-down creature entered the battlefield under your control this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/u/UginsNexus.java b/Mage.Sets/src/mage/cards/u/UginsNexus.java index 7722a23d950..6069ea6d735 100644 --- a/Mage.Sets/src/mage/cards/u/UginsNexus.java +++ b/Mage.Sets/src/mage/cards/u/UginsNexus.java @@ -1,10 +1,9 @@ - package mage.cards.u; import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SkipExtraTurnsAbility; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; import mage.cards.CardImpl; @@ -12,10 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; -import mage.players.Player; /** * @@ -28,7 +25,7 @@ public final class UginsNexus extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // If a player would begin an extra turn, that player skips that turn instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new UginsNexusSkipExtraTurnsEffect())); + this.addAbility(new SkipExtraTurnsAbility()); // If Ugin's Nexus would be put into a graveyard from the battlefield, instead exile it and take an extra turn after this one. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new UginsNexusExileEffect())); @@ -44,44 +41,6 @@ public final class UginsNexus extends CardImpl { } } -class UginsNexusSkipExtraTurnsEffect extends ReplacementEffectImpl { - - UginsNexusSkipExtraTurnsEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "If a player would begin an extra turn, that player skips that turn instead"; - } - - private UginsNexusSkipExtraTurnsEffect(final UginsNexusSkipExtraTurnsEffect effect) { - super(effect); - } - - @Override - public UginsNexusSkipExtraTurnsEffect copy() { - return new UginsNexusSkipExtraTurnsEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - MageObject sourceObject = game.getObject(source); - if (player != null && sourceObject != null) { - game.informPlayers(sourceObject.getLogName() + ": Extra turn of " + player.getLogName() + " skipped"); - } - return true; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.EXTRA_TURN; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return true; - } - -} - class UginsNexusExileEffect extends ReplacementEffectImpl { UginsNexusExileEffect() { @@ -124,5 +83,4 @@ class UginsNexusExileEffect extends ReplacementEffectImpl { } return false; } - } diff --git a/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java b/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java index 9e8ff01ba6d..3eb729782f1 100644 --- a/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java +++ b/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java @@ -85,7 +85,7 @@ class UglukOfTheWhiteHandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent deadCreature = game.getPermanentOrLKIBattlefield(targetPointer.getFirst(game, source)); + Permanent deadCreature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); boolean wasOrcOrGoblin = false; if (deadCreature != null) { diff --git a/Mage.Sets/src/mage/cards/u/UlamogTheCeaselessHunger.java b/Mage.Sets/src/mage/cards/u/UlamogTheCeaselessHunger.java index 12c36f197ee..2fd36db8c4f 100644 --- a/Mage.Sets/src/mage/cards/u/UlamogTheCeaselessHunger.java +++ b/Mage.Sets/src/mage/cards/u/UlamogTheCeaselessHunger.java @@ -143,7 +143,7 @@ class UlamogExileLibraryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player defender = game.getPlayer(targetPointer.getFirst(game, source)); + Player defender = game.getPlayer(getTargetPointer().getFirst(game, source)); if (defender != null) { defender.moveCards(defender.getLibrary().getTopCards(game, 20), Zone.EXILED, source, game); return true; diff --git a/Mage.Sets/src/mage/cards/u/UlvenwaldMysteries.java b/Mage.Sets/src/mage/cards/u/UlvenwaldMysteries.java index a2632f44c6d..605de3d69b9 100644 --- a/Mage.Sets/src/mage/cards/u/UlvenwaldMysteries.java +++ b/Mage.Sets/src/mage/cards/u/UlvenwaldMysteries.java @@ -7,9 +7,7 @@ import mage.abilities.effects.keyword.InvestigateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.HumanSoldierToken; import java.util.UUID; @@ -20,12 +18,6 @@ import java.util.UUID; */ public final class UlvenwaldMysteries extends CardImpl { - private static final FilterControlledPermanent filterClue = new FilterControlledPermanent("a Clue"); - - static { - filterClue.add(SubType.CLUE.getPredicate()); - } - public UlvenwaldMysteries(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}"); @@ -33,7 +25,7 @@ public final class UlvenwaldMysteries extends CardImpl { this.addAbility(new DiesCreatureTriggeredAbility(new InvestigateEffect(), false, StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN)); // Whenever you sacrifice a Clue, create a 1/1 white Human Soldier creature token. - this.addAbility(new SacrificePermanentTriggeredAbility(new CreateTokenEffect(new HumanSoldierToken()), filterClue)); + this.addAbility(new SacrificePermanentTriggeredAbility(new CreateTokenEffect(new HumanSoldierToken()), StaticFilters.FILTER_CONTROLLED_CLUE)); } private UlvenwaldMysteries(final UlvenwaldMysteries card) { diff --git a/Mage.Sets/src/mage/cards/u/Umbilicus.java b/Mage.Sets/src/mage/cards/u/Umbilicus.java index d802eca9fbe..820e5e3e6fc 100644 --- a/Mage.Sets/src/mage/cards/u/Umbilicus.java +++ b/Mage.Sets/src/mage/cards/u/Umbilicus.java @@ -58,7 +58,7 @@ class BloodClockEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/u/UncheckedGrowth.java b/Mage.Sets/src/mage/cards/u/UncheckedGrowth.java index 3ac4c86d41c..1e2fcecddcc 100644 --- a/Mage.Sets/src/mage/cards/u/UncheckedGrowth.java +++ b/Mage.Sets/src/mage/cards/u/UncheckedGrowth.java @@ -60,7 +60,7 @@ public final class UncheckedGrowth extends CardImpl { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null && permanent.hasSubtype(SubType.SPIRIT, game)) { permanent.addAbility(TrampleAbility.getInstance(), source.getSourceId(), game); diff --git a/Mage.Sets/src/mage/cards/u/UnifyingTheory.java b/Mage.Sets/src/mage/cards/u/UnifyingTheory.java index 660e0f050cd..5bb815b8770 100644 --- a/Mage.Sets/src/mage/cards/u/UnifyingTheory.java +++ b/Mage.Sets/src/mage/cards/u/UnifyingTheory.java @@ -60,7 +60,7 @@ class UnifyingTheoryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player caster = game.getPlayer(targetPointer.getFirst(game, source)); + Player caster = game.getPlayer(getTargetPointer().getFirst(game, source)); if (caster != null) { if (caster.chooseUse(Outcome.DrawCard, "Pay {2} to draw a card?", source, game)) { Cost cost = new ManaCostsImpl<>("{2}"); diff --git a/Mage.Sets/src/mage/cards/u/UnscrupulousAgent.java b/Mage.Sets/src/mage/cards/u/UnscrupulousAgent.java new file mode 100644 index 00000000000..c1e45495f12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnscrupulousAgent.java @@ -0,0 +1,44 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileFromZoneTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class UnscrupulousAgent extends CardImpl { + + public UnscrupulousAgent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Unscrupulous Agent enters the battlefield, target opponent exiles a card from their hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileFromZoneTargetEffect(Zone.HAND, false)); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + } + + private UnscrupulousAgent(final UnscrupulousAgent card) { + super(card); + } + + @Override + public UnscrupulousAgent copy() { + return new UnscrupulousAgent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java b/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java index e8e5fcf7b7f..3656efb6987 100644 --- a/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java +++ b/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java @@ -128,7 +128,7 @@ class UnscytheEffect extends OneShotEffect { if (controller == null) { return false; } - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/u/UnshakableTail.java b/Mage.Sets/src/mage/cards/u/UnshakableTail.java new file mode 100644 index 00000000000..a51784853ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnshakableTail.java @@ -0,0 +1,137 @@ +package mage.cards.u; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.Card; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeGroupEvent; + +/** + * + * @author kleese + */ +public final class UnshakableTail extends CardImpl { + + public UnshakableTail(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Unshakable Tail enters the battlefield and at the beginning of your upkeep, surveil 1. + this.addAbility(new UnshakableTailSurveilTriggeredAbility()); + + // Whenever one or more creature cards are put into your graveyard from your library, investigate. + this.addAbility(new UnshakableTailInvestigateTriggeredAbility()); + + // {2}, Sacrifice a Clue: Return Unshakable Tail from your graveyard to your hand. + Ability ability = new SimpleActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), new ManaCostsImpl<>("{2}") + ); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CLUE)); + this.addAbility(ability); + } + + private UnshakableTail(final UnshakableTail card) { + super(card); + } + + @Override + public UnshakableTail copy() { + return new UnshakableTail(this); + } +} + +class UnshakableTailSurveilTriggeredAbility extends TriggeredAbilityImpl { + + UnshakableTailSurveilTriggeredAbility() { + super(Zone.BATTLEFIELD, new SurveilEffect(1, false)); + setTriggerPhrase("When {this} enters the battlefield and at the beginning of your upkeep, "); + } + + private UnshakableTailSurveilTriggeredAbility(final UnshakableTailSurveilTriggeredAbility ability) { + super(ability); + } + + @Override + public UnshakableTailSurveilTriggeredAbility copy() { + return new UnshakableTailSurveilTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + return event.getTargetId().equals(getSourceId()); + } + return game.isActivePlayer(getControllerId()); + } + +} + +class UnshakableTailInvestigateTriggeredAbility extends TriggeredAbilityImpl { + + UnshakableTailInvestigateTriggeredAbility() { + super(Zone.BATTLEFIELD, new InvestigateEffect(), false); + setTriggerPhrase("Whenever one or more creature cards are put into your graveyard from your library, "); + } + + private UnshakableTailInvestigateTriggeredAbility(final UnshakableTailInvestigateTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event; + if (zEvent != null + && Zone.LIBRARY == zEvent.getFromZone() + && Zone.GRAVEYARD == zEvent.getToZone() + && zEvent.getCards() != null) { + for (Card card : zEvent.getCards()) { + if (card != null) { + UUID cardOwnerId = card.getOwnerId(); + if (cardOwnerId != null + && card.isOwnedBy(getControllerId()) + && card.isCreature(game)) { + return true; + } + } + + } + } + return false; + } + + @Override + public UnshakableTailInvestigateTriggeredAbility copy() { + return new UnshakableTailInvestigateTriggeredAbility(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/u/UrtetRemnantOfMemnarch.java b/Mage.Sets/src/mage/cards/u/UrtetRemnantOfMemnarch.java index 7f27c3958ed..ae8c8df9f6a 100644 --- a/Mage.Sets/src/mage/cards/u/UrtetRemnantOfMemnarch.java +++ b/Mage.Sets/src/mage/cards/u/UrtetRemnantOfMemnarch.java @@ -17,7 +17,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterSpell; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.MyrToken; import java.util.UUID; @@ -28,7 +28,7 @@ import java.util.UUID; public final class UrtetRemnantOfMemnarch extends CardImpl { private static final FilterSpell filter = new FilterSpell("a Myr spell"); - private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent(SubType.MYR); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.MYR); static { filter.add(SubType.MYR.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/u/UrzasBauble.java b/Mage.Sets/src/mage/cards/u/UrzasBauble.java index 84b9ae16c10..2dc4b4b79f1 100644 --- a/Mage.Sets/src/mage/cards/u/UrzasBauble.java +++ b/Mage.Sets/src/mage/cards/u/UrzasBauble.java @@ -35,7 +35,7 @@ public final class UrzasBauble extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new LookAtRandomCardEffect(), new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetPlayer()); - ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1, "you")), false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/u/UtopiaMycon.java b/Mage.Sets/src/mage/cards/u/UtopiaMycon.java index 57e5e5efacb..f5a7c3d2184 100644 --- a/Mage.Sets/src/mage/cards/u/UtopiaMycon.java +++ b/Mage.Sets/src/mage/cards/u/UtopiaMycon.java @@ -16,9 +16,8 @@ import mage.constants.SubType; import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; @@ -28,11 +27,7 @@ import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; */ public final class UtopiaMycon extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Saproling"); - - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SAPROLING, "Saproling"); public UtopiaMycon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); diff --git a/Mage.Sets/src/mage/cards/v/VastwoodAnimist.java b/Mage.Sets/src/mage/cards/v/VastwoodAnimist.java index 8223589591d..2d22a1a8a19 100644 --- a/Mage.Sets/src/mage/cards/v/VastwoodAnimist.java +++ b/Mage.Sets/src/mage/cards/v/VastwoodAnimist.java @@ -76,7 +76,7 @@ class VastwoodAnimistEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int amount = new PermanentsOnBattlefieldCount(filterAllies).calculate(game, source, this); ContinuousEffect effect = new BecomesCreatureTargetEffect(new VastwoodAnimistElementalToken(amount), false, true, Duration.EndOfTurn); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); game.addEffect(effect, source); return false; } diff --git a/Mage.Sets/src/mage/cards/v/VatEmergence.java b/Mage.Sets/src/mage/cards/v/VatEmergence.java index 3a6d06b12ab..85820429eeb 100644 --- a/Mage.Sets/src/mage/cards/v/VatEmergence.java +++ b/Mage.Sets/src/mage/cards/v/VatEmergence.java @@ -6,6 +6,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; @@ -16,14 +17,12 @@ import java.util.UUID; */ public final class VatEmergence extends CardImpl { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public VatEmergence(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); // Put target creature card from a graveyard onto the battlefield under your control. Proliferate. this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.getSpellAbility().addEffect(new ProliferateEffect()); } diff --git a/Mage.Sets/src/mage/cards/v/VenarianGlimmer.java b/Mage.Sets/src/mage/cards/v/VenarianGlimmer.java index 2798938d75d..e8702a9aec5 100644 --- a/Mage.Sets/src/mage/cards/v/VenarianGlimmer.java +++ b/Mage.Sets/src/mage/cards/v/VenarianGlimmer.java @@ -60,12 +60,12 @@ class VenarianGlimmerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { FilterCard filter = new FilterNonlandCard(); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1)); Effect effect = new DiscardCardYouChooseTargetEffect(filter); - effect.setTargetPointer(targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.apply(game, source); return true; } diff --git a/Mage.Sets/src/mage/cards/v/VendilionClique.java b/Mage.Sets/src/mage/cards/v/VendilionClique.java index 105055d2849..1ff9aa4e669 100644 --- a/Mage.Sets/src/mage/cards/v/VendilionClique.java +++ b/Mage.Sets/src/mage/cards/v/VendilionClique.java @@ -72,7 +72,7 @@ class VendilionCliqueEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (player != null && controller != null && sourceObject != null) { diff --git a/Mage.Sets/src/mage/cards/v/Venom.java b/Mage.Sets/src/mage/cards/v/Venom.java index d01d9dd343b..4861922dbb8 100644 --- a/Mage.Sets/src/mage/cards/v/Venom.java +++ b/Mage.Sets/src/mage/cards/v/Venom.java @@ -42,7 +42,7 @@ public final class Venom extends CardImpl { // Whenever enchanted creature blocks or becomes blocked by a non-Wall creature, destroy the other creature at end of combat. Effect effect = new CreateDelayedTriggeredAbilityEffect( new AtTheEndOfCombatDelayedTriggeredAbility(new DestroyTargetEffect()), true); - effect.setText("destroy that creature at end of combat"); + effect.setText("destroy the other creature at end of combat"); this.addAbility(new VenomTriggeredAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/v/VeryCrypticCommandD.java b/Mage.Sets/src/mage/cards/v/VeryCrypticCommandD.java index 7fc697e03a4..7286e6f419a 100644 --- a/Mage.Sets/src/mage/cards/v/VeryCrypticCommandD.java +++ b/Mage.Sets/src/mage/cards/v/VeryCrypticCommandD.java @@ -103,9 +103,20 @@ class TurnOverEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent creature = game.getPermanent(source.getFirstTarget()); if (creature != null) { + // To turn over a creature means to physically turn the card over. If the result is a face-down card, it’s + // a colorless 2/2 creature, the same as one would get from using the morph ability. Turning a face-down + // card over results in it being turned face up. Any abilities that trigger when it’s turned face up will + // work. Turning a double-faced card over is the same as transforming it. Any abilities that trigger when + // you transform it will work. Turning a combined host/augment creature over will result in a big colorless + // 2/2 creature represented by two cards. Turning a melded creature over will result in the two cards + // breaking apart and forming two separate creatures, but they’ll probably just get right back together. + // Turning B.F.M. (Big Furry Monster) over is the same as turning a combined creature over. + // (2018-01-19) if (creature.isFaceDown(game)) { + // face down -> face up creature.turnFaceUp(source, game, source.getControllerId()); } else { + // face up -> face down without face up ability creature.turnFaceDown(source, game, source.getControllerId()); MageObjectReference objectReference = new MageObjectReference(creature.getId(), creature.getZoneChangeCounter(game), game); game.addEffect(new BecomesFaceDownCreatureEffect(null, objectReference, Duration.Custom, FaceDownType.MANUAL), source); diff --git a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java index 93d43cca6a9..685b33c6f5a 100644 --- a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java +++ b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java @@ -161,7 +161,8 @@ class VesuvanShapeshifterFaceDownEffect extends OneShotEffect { permanent.turnFaceDown(source, game, source.getControllerId()); permanent.setManifested(false); - permanent.setMorphed(true); + permanent.setDisguised(false); + permanent.setMorphed(true); // cause it morph card TODO: smells bad return permanent.isFaceDown(game); } diff --git a/Mage.Sets/src/mage/cards/v/VexingArcanix.java b/Mage.Sets/src/mage/cards/v/VexingArcanix.java index 43d0402826f..a46f38e7a5a 100644 --- a/Mage.Sets/src/mage/cards/v/VexingArcanix.java +++ b/Mage.Sets/src/mage/cards/v/VexingArcanix.java @@ -60,7 +60,7 @@ class VexingArcanixEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { MageObject sourceObject = source.getSourceObject(game); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (sourceObject == null || player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/v/VexingShusher.java b/Mage.Sets/src/mage/cards/v/VexingShusher.java index 1a7584b34ba..65fedef1e63 100644 --- a/Mage.Sets/src/mage/cards/v/VexingShusher.java +++ b/Mage.Sets/src/mage/cards/v/VexingShusher.java @@ -85,7 +85,7 @@ class VexingShusherCantCounterTargetEffect extends ContinuousRuleModifyingEffect @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(targetPointer.getFirst(game, source)); + return event.getTargetId().equals(getTargetPointer().getFirst(game, source)); } } diff --git a/Mage.Sets/src/mage/cards/v/Vexis.java b/Mage.Sets/src/mage/cards/v/Vexis.java index b13104e915d..460ed9259fa 100644 --- a/Mage.Sets/src/mage/cards/v/Vexis.java +++ b/Mage.Sets/src/mage/cards/v/Vexis.java @@ -2,21 +2,15 @@ package mage.cards.v; import mage.MageInt; import mage.abilities.common.OneOrMoreCountersAddedTriggeredAbility; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.mana.ColoredManaCost; -import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.MonstrosityAbility; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.VigilanceAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; -import mage.counters.CounterType; import java.util.UUID; @@ -38,7 +32,7 @@ public class Vexis extends CardImpl { //Whenever a +1/+1 counter is put on Vexis, it gains vigilance until end of turn. this.addAbility(new OneOrMoreCountersAddedTriggeredAbility(new GainAbilitySourceEffect( - VigilanceAbility.getInstance(), Duration.EndOfTurn), false, CounterType.P1P1)); + VigilanceAbility.getInstance(), Duration.EndOfTurn))); } private Vexis(final Vexis card) { diff --git a/Mage.Sets/src/mage/cards/v/VileRebirth.java b/Mage.Sets/src/mage/cards/v/VileRebirth.java index 6882ecaa763..5af900ac0d3 100644 --- a/Mage.Sets/src/mage/cards/v/VileRebirth.java +++ b/Mage.Sets/src/mage/cards/v/VileRebirth.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.game.permanent.token.ZombieToken; import mage.target.common.TargetCardInGraveyard; @@ -22,7 +23,7 @@ public final class VileRebirth extends CardImpl { // Exile target creature card from a graveyard. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); // Create a 2/2 black Zombie creature token. this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken())); diff --git a/Mage.Sets/src/mage/cards/v/VileRedeemer.java b/Mage.Sets/src/mage/cards/v/VileRedeemer.java index fc390ec7930..f4713a13b44 100644 --- a/Mage.Sets/src/mage/cards/v/VileRedeemer.java +++ b/Mage.Sets/src/mage/cards/v/VileRedeemer.java @@ -63,7 +63,7 @@ class VileRedeemerEffect extends OneShotEffect { VileRedeemerEffect() { super(Outcome.PutCreatureInPlay); - this.staticText = "create a 1/1 colorless Eldrazi Scion creature token for each nontoken creature that died under your control this turn. They have \"Sacrifice this creature: Add {C}"; + this.staticText = "create a 1/1 colorless Eldrazi Scion creature token for each nontoken creature that died under your control this turn. Those tokens have \"Sacrifice this creature: Add {C}.\""; } private VileRedeemerEffect(final VileRedeemerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/v/VillageReavers.java b/Mage.Sets/src/mage/cards/v/VillageReavers.java index ccdf2918732..632bcfddb62 100644 --- a/Mage.Sets/src/mage/cards/v/VillageReavers.java +++ b/Mage.Sets/src/mage/cards/v/VillageReavers.java @@ -11,7 +11,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; import java.util.UUID; @@ -21,7 +21,7 @@ import java.util.UUID; */ public final class VillageReavers extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent("Wolves and Werewolves"); + private static final FilterPermanent filter = new FilterControlledPermanent("Wolves and Werewolves"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/v/VillainousWealth.java b/Mage.Sets/src/mage/cards/v/VillainousWealth.java index b3affaa99ee..415fdb7bb79 100644 --- a/Mage.Sets/src/mage/cards/v/VillainousWealth.java +++ b/Mage.Sets/src/mage/cards/v/VillainousWealth.java @@ -64,7 +64,7 @@ class VillainousWealthEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); int xValue = source.getManaCostsToPay().getX(); if (controller == null || opponent == null || xValue < 1) { return false; diff --git a/Mage.Sets/src/mage/cards/v/ViridescentBog.java b/Mage.Sets/src/mage/cards/v/ViridescentBog.java new file mode 100644 index 00000000000..991af206273 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ViridescentBog.java @@ -0,0 +1,37 @@ +package mage.cards.v; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ViridescentBog extends CardImpl { + + public ViridescentBog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {1}, {T}: Add {B}{G}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new Mana(0, 0, 1, 0, 1, 0, 0, 0), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private ViridescentBog(final ViridescentBog card) { + super(card); + } + + @Override + public ViridescentBog copy() { + return new ViridescentBog(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VirtueOfPersistence.java b/Mage.Sets/src/mage/cards/v/VirtueOfPersistence.java index b2d0edccc40..180ad65bb4e 100644 --- a/Mage.Sets/src/mage/cards/v/VirtueOfPersistence.java +++ b/Mage.Sets/src/mage/cards/v/VirtueOfPersistence.java @@ -11,6 +11,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.TargetController; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCreaturePermanent; @@ -22,8 +23,6 @@ import java.util.UUID; */ public final class VirtueOfPersistence extends AdventureCard { - private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); - public VirtueOfPersistence(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, new CardType[]{CardType.SORCERY}, "{5}{B}{B}", "Locthwain Scorn", "{1}{B}"); @@ -31,7 +30,7 @@ public final class VirtueOfPersistence extends AdventureCard { Ability ability = new BeginningOfUpkeepTriggeredAbility( new ReturnFromGraveyardToBattlefieldTargetEffect(), TargetController.YOU, false ); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); // Locthwain Scorn diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java b/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java index 9f260848d61..760d60e4679 100644 --- a/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java +++ b/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java @@ -1,18 +1,15 @@ package mage.cards.v; -import mage.abilities.Ability; import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.GreatestCommanderManaValue; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.FlashbackAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -26,7 +23,8 @@ public final class VisionsOfDominance extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); // Put a +1/+1 counter on target creature, then double the number of +1/+1 counters on it. - this.getSpellAbility().addEffect(new VisionsOfDominanceEffect()); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addEffect(new DoubleCountersTargetEffect(CounterType.P1P1).concatBy(", then")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {8}{G}{G}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. @@ -46,34 +44,3 @@ public final class VisionsOfDominance extends CardImpl { return new VisionsOfDominance(this); } } - -class VisionsOfDominanceEffect extends OneShotEffect { - - VisionsOfDominanceEffect() { - super(Outcome.Benefit); - staticText = "put a +1/+1 counter on target creature, then double the number of +1/+1 counters on it"; - } - - private VisionsOfDominanceEffect(final VisionsOfDominanceEffect effect) { - super(effect); - } - - @Override - public VisionsOfDominanceEffect copy() { - return new VisionsOfDominanceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent == null) { - return false; - } - permanent.addCounters(CounterType.P1P1.createInstance(), source, game); - int counterCount = permanent.getCounters(game).getCount(CounterType.P1P1); - if (counterCount > 0) { - permanent.addCounters(CounterType.P1P1.createInstance(counterCount), source, game); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/v/VitasporeThallid.java b/Mage.Sets/src/mage/cards/v/VitasporeThallid.java index 2b382dfe69d..829a62c7c47 100644 --- a/Mage.Sets/src/mage/cards/v/VitasporeThallid.java +++ b/Mage.Sets/src/mage/cards/v/VitasporeThallid.java @@ -16,9 +16,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.SaprolingToken; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; /** @@ -27,10 +26,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class VitasporeThallid extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Saproling"); - static { - filter.add(SubType.SAPROLING.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SAPROLING, "Saproling"); public VitasporeThallid(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); diff --git a/Mage.Sets/src/mage/cards/v/VodalianWarMachine.java b/Mage.Sets/src/mage/cards/v/VodalianWarMachine.java index f0264399c5f..fe4ab8aad6e 100644 --- a/Mage.Sets/src/mage/cards/v/VodalianWarMachine.java +++ b/Mage.Sets/src/mage/cards/v/VodalianWarMachine.java @@ -1,4 +1,3 @@ - package mage.cards.v; import mage.MageInt; @@ -15,14 +14,14 @@ import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import mage.watchers.Watcher; import java.util.*; @@ -32,7 +31,7 @@ import java.util.*; */ public final class VodalianWarMachine extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Merfolk you control"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Merfolk you control"); static { filter.add(TappedPredicate.UNTAPPED); @@ -49,11 +48,11 @@ public final class VodalianWarMachine extends CardImpl { this.addAbility(DefenderAbility.getInstance()); // Tap an untapped Merfolk you control: Vodalian War Machine can attack this turn as though it didn't have defender. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.EndOfTurn), new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, true))); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.EndOfTurn), new TapTargetCost(new TargetControlledPermanent(1, 1, filter, true))); this.addAbility(ability); // Tap an untapped Merfolk you control: Vodalian War Machine gets +2/+1 until end of turn. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(2, 1, Duration.EndOfTurn), new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, true)))); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(2, 1, Duration.EndOfTurn), new TapTargetCost(new TargetControlledPermanent(1, 1, filter, true)))); // When Vodalian War Machine dies, destroy all Merfolk tapped this turn to pay for its abilities. this.addAbility(new DiesSourceTriggeredAbility(new VodalianWarMachineEffect()), new VodalianWarMachineWatcher()); @@ -71,13 +70,13 @@ public final class VodalianWarMachine extends CardImpl { class VodalianWarMachineEffect extends OneShotEffect { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Merfolk tapped this turn to pay for its abilities"); + private static final FilterPermanent filter = new FilterPermanent("Merfolk tapped this turn to pay for its abilities"); static { filter.add(SubType.MERFOLK.getPredicate()); } - public VodalianWarMachineEffect() { + VodalianWarMachineEffect() { super(Outcome.Detriment); staticText = "destroy all " + filter.getMessage(); } @@ -112,13 +111,13 @@ class VodalianWarMachineEffect extends OneShotEffect { class VodalianWarMachineWatcher extends Watcher { - private Map> tappedMerfolkIds = new HashMap<>(); + private final Map> tappedMerfolkIds = new HashMap<>(); - public VodalianWarMachineWatcher() { + VodalianWarMachineWatcher() { super(WatcherScope.GAME); } - public Set getTappedMerfolkIds(Permanent permanent, Game game) { + Set getTappedMerfolkIds(Permanent permanent, Game game) { return tappedMerfolkIds.get(new MageObjectReference(permanent, game)); } diff --git a/Mage.Sets/src/mage/cards/v/Void.java b/Mage.Sets/src/mage/cards/v/Void.java index d0404a666d2..79062b8d7a4 100644 --- a/Mage.Sets/src/mage/cards/v/Void.java +++ b/Mage.Sets/src/mage/cards/v/Void.java @@ -77,7 +77,7 @@ class VoidEffect extends OneShotEffect { filterCard.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, number)); filterCard.add(Predicates.not(CardType.LAND.getPredicate())); - Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetPlayer == null) { return true; } diff --git a/Mage.Sets/src/mage/cards/v/VoidmageProdigy.java b/Mage.Sets/src/mage/cards/v/VoidmageProdigy.java index 8dc6f028a0c..72fa850bba4 100644 --- a/Mage.Sets/src/mage/cards/v/VoidmageProdigy.java +++ b/Mage.Sets/src/mage/cards/v/VoidmageProdigy.java @@ -1,4 +1,3 @@ - package mage.cards.v; import java.util.UUID; @@ -14,10 +13,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.target.Target; import mage.target.TargetSpell; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -25,11 +23,7 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class VoidmageProdigy extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a Wizard"); - - static { - filter.add(SubType.WIZARD.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.WIZARD, "a Wizard"); public VoidmageProdigy(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{U}{U}"); diff --git a/Mage.Sets/src/mage/cards/v/VolcanicEruption.java b/Mage.Sets/src/mage/cards/v/VolcanicEruption.java index 4c6fdc685b2..7330d0f7bec 100644 --- a/Mage.Sets/src/mage/cards/v/VolcanicEruption.java +++ b/Mage.Sets/src/mage/cards/v/VolcanicEruption.java @@ -75,7 +75,7 @@ class VolcanicEruptionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int destroyedCount = 0; - for (UUID targetID : this.targetPointer.getTargets(game, source)) { + for (UUID targetID : this.getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetID); if (permanent != null) { if (permanent.destroy(source, game, false)) { diff --git a/Mage.Sets/src/mage/cards/v/VolcanicVision.java b/Mage.Sets/src/mage/cards/v/VolcanicVision.java index cb8264efa39..8ba271af233 100644 --- a/Mage.Sets/src/mage/cards/v/VolcanicVision.java +++ b/Mage.Sets/src/mage/cards/v/VolcanicVision.java @@ -72,7 +72,7 @@ class VolcanicVisionReturnToHandTargetEffect extends OneShotEffect { if (controller == null) { return false; } - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { switch (game.getState().getZone(targetId)) { case GRAVEYARD: Card card = game.getCard(targetId); diff --git a/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java b/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java index 8a98f91aeaa..968c9e20b67 100644 --- a/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java +++ b/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java @@ -91,7 +91,6 @@ class VolrathTheShapestealerEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Permanent volrathTheShapestealer = game.getPermanent(source.getSourceId()); - Permanent newBluePrint = null; if (controller == null || volrathTheShapestealer == null) { return false; @@ -100,12 +99,12 @@ class VolrathTheShapestealerEffect extends OneShotEffect { if (copyFromCard == null) { return true; } - newBluePrint = new PermanentCard(copyFromCard, source.getControllerId(), game); + //newBluePrint = new PermanentCard(copyFromCard, source.getControllerId(), game); + Card newBluePrint = copyFromCard.copy(); newBluePrint.assignNewId(); CopyApplier applier = new VolrathTheShapestealerCopyApplier(); applier.apply(game, newBluePrint, source, volrathTheShapestealer.getId()); CopyEffect copyEffect = new CopyEffect(Duration.UntilYourNextTurn, newBluePrint, volrathTheShapestealer.getId()); - copyEffect.newId(); copyEffect.setApplier(applier); Ability newAbility = source.copy(); copyEffect.init(newAbility, game); diff --git a/Mage.Sets/src/mage/cards/v/VoraciousVampire.java b/Mage.Sets/src/mage/cards/v/VoraciousVampire.java index d680395895a..30b2e887ba3 100644 --- a/Mage.Sets/src/mage/cards/v/VoraciousVampire.java +++ b/Mage.Sets/src/mage/cards/v/VoraciousVampire.java @@ -1,7 +1,5 @@ - package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -14,8 +12,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; /** * @@ -23,6 +23,8 @@ import mage.target.common.TargetControlledCreaturePermanent; */ public final class VoraciousVampire extends CardImpl { + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.VAMPIRE, "Vampire you control"); + public VoraciousVampire(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); @@ -40,7 +42,7 @@ public final class VoraciousVampire extends CardImpl { Effect effect = new GainAbilityTargetEffect(new MenaceAbility(), Duration.EndOfTurn); effect.setText("and gains menace until end of turn."); ability.addEffect(effect); - ability.addTarget(new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent(SubType.VAMPIRE, "Vampire you control"))); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VraskaRelicSeeker.java b/Mage.Sets/src/mage/cards/v/VraskaRelicSeeker.java index 833efd7968f..3a35e599954 100644 --- a/Mage.Sets/src/mage/cards/v/VraskaRelicSeeker.java +++ b/Mage.Sets/src/mage/cards/v/VraskaRelicSeeker.java @@ -72,7 +72,7 @@ class VraskaRelicSeekerLifeTotalEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.setLife(1, game, source); return true; diff --git a/Mage.Sets/src/mage/cards/w/WailOfTheNim.java b/Mage.Sets/src/mage/cards/w/WailOfTheNim.java index 9fab248aff3..2e701630794 100644 --- a/Mage.Sets/src/mage/cards/w/WailOfTheNim.java +++ b/Mage.Sets/src/mage/cards/w/WailOfTheNim.java @@ -1,4 +1,3 @@ - package mage.cards.w; import java.util.UUID; @@ -9,7 +8,7 @@ import mage.abilities.keyword.EntwineAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -21,7 +20,7 @@ public final class WailOfTheNim extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{B}"); // Choose one - Regenerate each creature you control; - this.getSpellAbility().addEffect(new RegenerateAllEffect(new FilterControlledCreaturePermanent())); + this.getSpellAbility().addEffect(new RegenerateAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE)); // or Wail of the Nim deals 1 damage to each creature and each player. Mode mode = new Mode(new DamageEverythingEffect(1)); diff --git a/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java b/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java index 8b84c6a0707..2fa47047c53 100644 --- a/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java +++ b/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java @@ -106,13 +106,13 @@ class WakeToSlaughterEffect extends OneShotEffect { } else { player.moveCards(card, Zone.BATTLEFIELD, source, game); - FixedTarget fixedTarget = new FixedTarget(card, game); + FixedTarget blueprintTarget = new FixedTarget(card, game); ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfGame); - effect.setTargetPointer(fixedTarget); + effect.setTargetPointer(blueprintTarget.copy()); game.addEffect(effect, source); ExileTargetEffect exileEffect = new ExileTargetEffect(null, null, Zone.BATTLEFIELD); - exileEffect.setTargetPointer(fixedTarget); + exileEffect.setTargetPointer(blueprintTarget.copy()); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect); game.addDelayedTriggeredAbility(delayedAbility, source); } diff --git a/Mage.Sets/src/mage/cards/w/WalkerOfSecretWays.java b/Mage.Sets/src/mage/cards/w/WalkerOfSecretWays.java index b946129ec65..64e85962af2 100644 --- a/Mage.Sets/src/mage/cards/w/WalkerOfSecretWays.java +++ b/Mage.Sets/src/mage/cards/w/WalkerOfSecretWays.java @@ -16,10 +16,10 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -28,11 +28,7 @@ import java.util.UUID; */ public final class WalkerOfSecretWays extends CardImpl { - private static final FilterControlledCreaturePermanent filterCreature = new FilterControlledCreaturePermanent("Ninja you control"); - - static { - filterCreature.add((SubType.NINJA.getPredicate())); - } + private static final FilterControlledPermanent filterCreature = new FilterControlledPermanent(SubType.NINJA, "Ninja you control"); public WalkerOfSecretWays(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); @@ -50,7 +46,7 @@ public final class WalkerOfSecretWays extends CardImpl { // {1}{U}: Return target Ninja you control to its owner's hand. Activate this ability only during your turn. Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new ManaCostsImpl<>("{1}{U}"), MyTurnCondition.instance); - ability.addTarget(new TargetControlledCreaturePermanent(1, 1, filterCreature, false)); + ability.addTarget(new TargetControlledPermanent(filterCreature)); ability.addHint(MyTurnHint.instance); this.addAbility(ability); @@ -80,7 +76,7 @@ class WalkerOfSecretWaysEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null && controller != null) { controller.lookAtCards("Walker of Secret Ways", player.getHand(), game); } @@ -92,4 +88,4 @@ class WalkerOfSecretWaysEffect extends OneShotEffect { return new WalkerOfSecretWaysEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/w/WallOfHope.java b/Mage.Sets/src/mage/cards/w/WallOfHope.java index 05cfbb1cd92..737d73d994a 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfHope.java +++ b/Mage.Sets/src/mage/cards/w/WallOfHope.java @@ -1,22 +1,16 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.players.Player; + +import java.util.UUID; /** * @@ -33,7 +27,7 @@ public final class WallOfHope extends CardImpl { // Defender this.addAbility(DefenderAbility.getInstance()); // Whenever Wall of Hope is dealt damage, you gain that much life. - this.addAbility(new WallOfHopeTriggeredAbility()); + this.addAbility(new DealtDamageToSourceTriggeredAbility(new GainLifeEffect(SavedDamageValue.MUCH), false)); } @@ -46,61 +40,3 @@ public final class WallOfHope extends CardImpl { return new WallOfHope(this); } } - -class WallOfHopeTriggeredAbility extends TriggeredAbilityImpl { - - public WallOfHopeTriggeredAbility() { - super(Zone.BATTLEFIELD, new WallOfHopeGainLifeEffect()); - setTriggerPhrase("Whenever {this} is dealt damage, "); - } - - private WallOfHopeTriggeredAbility(final WallOfHopeTriggeredAbility effect) { - super(effect); - } - - @Override - public WallOfHopeTriggeredAbility copy() { - return new WallOfHopeTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getTargetId().equals(this.sourceId)) { - return false; - } - this.getEffects().setValue("damageAmount", event.getAmount()); - return true; - } -} - -class WallOfHopeGainLifeEffect extends OneShotEffect { - - WallOfHopeGainLifeEffect() { - super(Outcome.GainLife); - staticText = "you gain that much life"; - } - - private WallOfHopeGainLifeEffect(final WallOfHopeGainLifeEffect effect) { - super(effect); - } - - @Override - public WallOfHopeGainLifeEffect copy() { - return new WallOfHopeGainLifeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife((Integer) this.getValue("damageAmount"), game, source); - } - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/w/WandOfIth.java b/Mage.Sets/src/mage/cards/w/WandOfIth.java index cac9985702f..bba33d38036 100644 --- a/Mage.Sets/src/mage/cards/w/WandOfIth.java +++ b/Mage.Sets/src/mage/cards/w/WandOfIth.java @@ -58,7 +58,7 @@ class WandOfIthEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (player != null && !player.getHand().isEmpty()) { Cards revealed = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/w/WanderingArchaic.java b/Mage.Sets/src/mage/cards/w/WanderingArchaic.java index 796030d6346..e31b06ad2f8 100644 --- a/Mage.Sets/src/mage/cards/w/WanderingArchaic.java +++ b/Mage.Sets/src/mage/cards/w/WanderingArchaic.java @@ -80,7 +80,7 @@ class WanderingArchaicEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); Spell spell = (Spell) getValue("spellCast"); if (controller == null || opponent == null || spell == null) { return false; diff --git a/Mage.Sets/src/mage/cards/w/WardenOfTheEye.java b/Mage.Sets/src/mage/cards/w/WardenOfTheEye.java index 06c72280f86..c74561e9190 100644 --- a/Mage.Sets/src/mage/cards/w/WardenOfTheEye.java +++ b/Mage.Sets/src/mage/cards/w/WardenOfTheEye.java @@ -1,7 +1,5 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -10,24 +8,22 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; -import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class WardenOfTheEye extends CardImpl { - private static final FilterCard filter = new FilterCard("noncreature, nonland card from your graveyard"); + private static final FilterCard filter = new FilterNonlandCard("noncreature, nonland card from your graveyard"); static { - filter.add(TargetController.YOU.getOwnerPredicate()); filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - filter.add(Predicates.not(CardType.LAND.getPredicate())); } public WardenOfTheEye(UUID ownerId, CardSetInfo setInfo) { @@ -40,7 +36,7 @@ public final class WardenOfTheEye extends CardImpl { // When Warden of the Eye enters the battlefield, return target noncreature, nonland card from your graveyard to your hand. Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); - ability.addTarget(new TargetCard(Zone.GRAVEYARD, filter)); + ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/w/WasitoraNekoruQueen.java b/Mage.Sets/src/mage/cards/w/WasitoraNekoruQueen.java index d764d412bc1..dfa7bd2f0df 100644 --- a/Mage.Sets/src/mage/cards/w/WasitoraNekoruQueen.java +++ b/Mage.Sets/src/mage/cards/w/WasitoraNekoruQueen.java @@ -77,7 +77,7 @@ class WasitoraNekoruQueenEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); + Player damagedPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (damagedPlayer != null && controller != null) { TargetSacrifice target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); diff --git a/Mage.Sets/src/mage/cards/w/WatcherOfHours.java b/Mage.Sets/src/mage/cards/w/WatcherOfHours.java new file mode 100644 index 00000000000..5f83a9d1dbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WatcherOfHours.java @@ -0,0 +1,51 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.CounterRemovedFromSourceWhileExiledTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.SuspendAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class WatcherOfHours extends CardImpl { + + public WatcherOfHours(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}"); + this.subtype.add(SubType.SPHINX); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward {3} + this.addAbility(new WardAbility(new GenericManaCost(3), false)); + + // Whenever you remove a time counter from Watcher of Hours while it's exiled, surveil 1. + this.addAbility(new CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType.TIME, new SurveilEffect(1, false), false, true)); + + // Suspend 6—{1}{U} + this.addAbility(new SuspendAbility(6, new ManaCostsImpl<>("{1}{U}"), this)); + } + + private WatcherOfHours(final WatcherOfHours card) { + super(card); + } + + @Override + public WatcherOfHours copy() { + return new WatcherOfHours(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WatchfulRadstag.java b/Mage.Sets/src/mage/cards/w/WatchfulRadstag.java new file mode 100644 index 00000000000..9ce66f51185 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WatchfulRadstag.java @@ -0,0 +1,72 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.CreateTokenCopySourceEffect; +import mage.abilities.keyword.EvolveAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WatchfulRadstag extends CardImpl { + + public WatchfulRadstag(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.ELK); + this.subtype.add(SubType.MUTANT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Evolve + this.addAbility(new EvolveAbility()); + + // Whenever Watchful Radstag evolves, create a token that's a copy of it. + this.addAbility(new WatchfulRadstagTriggeredAbility()); + } + + private WatchfulRadstag(final WatchfulRadstag card) { + super(card); + } + + @Override + public WatchfulRadstag copy() { + return new WatchfulRadstag(this); + } +} + +class WatchfulRadstagTriggeredAbility extends TriggeredAbilityImpl { + + WatchfulRadstagTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenCopySourceEffect().setText("create a token that's a copy of it"), false); + setTriggerPhrase("Whenever {this} evolves, "); + } + + private WatchfulRadstagTriggeredAbility(final WatchfulRadstagTriggeredAbility ability) { + super(ability); + } + + @Override + public WatchfulRadstagTriggeredAbility copy() { + return new WatchfulRadstagTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.EVOLVED_CREATURE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getTargetId().equals(getSourceId()); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java index 0c3d5e59be2..14226112377 100644 --- a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java +++ b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java @@ -101,22 +101,35 @@ enum WaytaTrainerProdigyAdjuster implements CostAdjuster { @Override public void adjustCosts(Ability ability, Game game) { - Target secondTarget = null; - for (Target target : ability.getTargets()){ - if (target.getTargetTag() == 2){ - secondTarget = target; - break; + if (game.inCheckPlayableState()) { + int controllerTargets = 0; //number of possible targets controlled by the ability's controller + for (UUID permId : CardUtil.getAllPossibleTargets(ability, game)) { + Permanent permanent = game.getPermanent(permId); + if (permanent != null && permanent.isControlledBy(ability.getControllerId())) { + controllerTargets++; + } + } + if (controllerTargets > 1) { + CardUtil.reduceCost(ability, 2); + } + } else { + Target secondTarget = null; + for (Target target : ability.getTargets()) { + if (target.getTargetTag() == 2) { + secondTarget = target; + break; + } + } + if (secondTarget == null) { + return; + } + // Having to call getFirstTarget() on a Target object called secondTarget + // (because it's the second target of a two-target ability) + // seems like an insult, but this is just getting the UUID of that target + Permanent permanent = game.getPermanentOrLKIBattlefield(secondTarget.getFirstTarget()); + if (permanent != null && permanent.isControlledBy(ability.getControllerId())) { + CardUtil.reduceCost(ability, 2); } - } - if (secondTarget == null){ - return; - } - // Having to call getFirstTarget() on a Target object called secondTarget - // (because it's the second target of a two-target ability) - // seems like an insult, but this is just getting the UUID of that target - Permanent permanent = game.getPermanentOrLKIBattlefield(secondTarget.getFirstTarget()); - if (permanent != null && permanent.isControlledBy(ability.getControllerId())) { - CardUtil.reduceCost(ability, 2); } } } diff --git a/Mage.Sets/src/mage/cards/w/WelcomeToTheFold.java b/Mage.Sets/src/mage/cards/w/WelcomeToTheFold.java index 80c058f4c48..6908b9bf6ce 100644 --- a/Mage.Sets/src/mage/cards/w/WelcomeToTheFold.java +++ b/Mage.Sets/src/mage/cards/w/WelcomeToTheFold.java @@ -61,6 +61,7 @@ class WelcomeToTheFoldEffect extends GainControlTargetEffect { @Override public void init(Ability source, Game game) { + super.init(source, game); Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { int maxToughness = 2; @@ -71,10 +72,8 @@ class WelcomeToTheFoldEffect extends GainControlTargetEffect { Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && permanent.getToughness().getValue() > maxToughness) { this.discard(); - return; } } - super.init(source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/w/WheelOfTorture.java b/Mage.Sets/src/mage/cards/w/WheelOfTorture.java index f328525b68c..112ac871457 100644 --- a/Mage.Sets/src/mage/cards/w/WheelOfTorture.java +++ b/Mage.Sets/src/mage/cards/w/WheelOfTorture.java @@ -49,7 +49,7 @@ class WheelOfTortureEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int amount = 3 - player.getHand().size(); if (amount > 0) { diff --git a/Mage.Sets/src/mage/cards/w/WhisperingSpecter.java b/Mage.Sets/src/mage/cards/w/WhisperingSpecter.java index 3fe3c8b1e07..6bc9fea537a 100644 --- a/Mage.Sets/src/mage/cards/w/WhisperingSpecter.java +++ b/Mage.Sets/src/mage/cards/w/WhisperingSpecter.java @@ -65,7 +65,7 @@ class WhisperingSpecterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { int value = player.getCounters().getCount(CounterType.POISON); if (value > 0) { diff --git a/Mage.Sets/src/mage/cards/w/WhitemaneLion.java b/Mage.Sets/src/mage/cards/w/WhitemaneLion.java index 31e50eafa14..3e112357faa 100644 --- a/Mage.Sets/src/mage/cards/w/WhitemaneLion.java +++ b/Mage.Sets/src/mage/cards/w/WhitemaneLion.java @@ -10,7 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -28,7 +28,7 @@ public final class WhitemaneLion extends CardImpl { // Flash this.addAbility(FlashAbility.getInstance()); // When Whitemane Lion enters the battlefield, return a creature you control to its owner's hand. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(new FilterControlledCreaturePermanent()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE))); } diff --git a/Mage.Sets/src/mage/cards/w/WildEvocation.java b/Mage.Sets/src/mage/cards/w/WildEvocation.java index 45b4a18e931..cafde7f223d 100644 --- a/Mage.Sets/src/mage/cards/w/WildEvocation.java +++ b/Mage.Sets/src/mage/cards/w/WildEvocation.java @@ -61,7 +61,7 @@ class WildEvocationEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (player != null && sourceObject != null) { diff --git a/Mage.Sets/src/mage/cards/w/WillowPriestess.java b/Mage.Sets/src/mage/cards/w/WillowPriestess.java index 68872c50106..40e46cf8c53 100644 --- a/Mage.Sets/src/mage/cards/w/WillowPriestess.java +++ b/Mage.Sets/src/mage/cards/w/WillowPriestess.java @@ -1,4 +1,3 @@ - package mage.cards.w; import java.util.UUID; @@ -29,7 +28,7 @@ import mage.target.TargetPermanent; */ public final class WillowPriestess extends CardImpl { - private static final FilterPermanentCard filter = new FilterPermanentCard("Faerie"); + private static final FilterPermanentCard filter = new FilterPermanentCard("a Faerie permanent card"); private static final FilterCreaturePermanent greenCreature = new FilterCreaturePermanent("green creature"); static { diff --git a/Mage.Sets/src/mage/cards/w/WindZendikon.java b/Mage.Sets/src/mage/cards/w/WindZendikon.java index 57ac77d6a60..8325b480e7f 100644 --- a/Mage.Sets/src/mage/cards/w/WindZendikon.java +++ b/Mage.Sets/src/mage/cards/w/WindZendikon.java @@ -1,4 +1,3 @@ - package mage.cards.w; import java.util.UUID; @@ -57,22 +56,24 @@ public final class WindZendikon extends CardImpl { return new WindZendikon(this); } - class WindZendikonElementalToken extends TokenImpl { - WindZendikonElementalToken() { - super("", "2/2 blue Elemental creature with flying"); - cardType.add(CardType.CREATURE); - color.setBlue(true); - subtype.add(SubType.ELEMENTAL); - power = new MageInt(2); - toughness = new MageInt(2); - addAbility(FlyingAbility.getInstance()); - } - private WindZendikonElementalToken(final WindZendikonElementalToken token) { - super(token); - } +} - public WindZendikonElementalToken copy() { - return new WindZendikonElementalToken(this); - } +class WindZendikonElementalToken extends TokenImpl { + + WindZendikonElementalToken() { + super("", "2/2 blue Elemental creature with flying"); + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.ELEMENTAL); + power = new MageInt(2); + toughness = new MageInt(2); + addAbility(FlyingAbility.getInstance()); + } + private WindZendikonElementalToken(final WindZendikonElementalToken token) { + super(token); + } + + public WindZendikonElementalToken copy() { + return new WindZendikonElementalToken(this); } } diff --git a/Mage.Sets/src/mage/cards/w/WingedTempleOfOrazca.java b/Mage.Sets/src/mage/cards/w/WingedTempleOfOrazca.java index c245e602697..4632a2078d3 100644 --- a/Mage.Sets/src/mage/cards/w/WingedTempleOfOrazca.java +++ b/Mage.Sets/src/mage/cards/w/WingedTempleOfOrazca.java @@ -73,7 +73,7 @@ class WingedTempleOfOrazcaEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (creature != null && creature.isCreature(game)) { int pow = creature.getPower().getValue(); ContinuousEffect effect = new BoostTargetEffect(pow, pow, Duration.EndOfTurn); diff --git a/Mage.Sets/src/mage/cards/w/WinterBlast.java b/Mage.Sets/src/mage/cards/w/WinterBlast.java index 8c0fe80e6cf..03bbb347250 100644 --- a/Mage.Sets/src/mage/cards/w/WinterBlast.java +++ b/Mage.Sets/src/mage/cards/w/WinterBlast.java @@ -68,7 +68,7 @@ class WinterBlastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { permanent.tap(source, game); diff --git a/Mage.Sets/src/mage/cards/w/WitchEngine.java b/Mage.Sets/src/mage/cards/w/WitchEngine.java index f321476c5b5..b76eb01a4be 100644 --- a/Mage.Sets/src/mage/cards/w/WitchEngine.java +++ b/Mage.Sets/src/mage/cards/w/WitchEngine.java @@ -1,28 +1,22 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.TargetPlayerGainControlSourceEffect; import mage.abilities.effects.mana.BasicManaEffect; import mage.abilities.keyword.SwampwalkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; import mage.constants.SubType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author fireshoes @@ -40,7 +34,7 @@ public final class WitchEngine extends CardImpl { // {T}: Add {B}{B}{B}{B}. Target opponent gains control of Witch Engine. (Activate this ability only any time you could cast an instant.) Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.BlackMana(4)), new TapSourceCost()); - ability.addEffect(new WitchEngineEffect()); + ability.addEffect(new TargetPlayerGainControlSourceEffect()); ability.addTarget(new TargetOpponent()); this.addAbility(ability); } @@ -54,32 +48,3 @@ public final class WitchEngine extends CardImpl { return new WitchEngine(this); } } - -class WitchEngineEffect extends ContinuousEffectImpl { - - WitchEngineEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - staticText = "target opponent gains control of {this}"; - } - - private WitchEngineEffect(final WitchEngineEffect effect) { - super(effect); - } - - @Override - public WitchEngineEffect copy() { - return new WitchEngineEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/w/WojekInvestigator.java b/Mage.Sets/src/mage/cards/w/WojekInvestigator.java index f59970278c9..4993b5fb578 100644 --- a/Mage.Sets/src/mage/cards/w/WojekInvestigator.java +++ b/Mage.Sets/src/mage/cards/w/WojekInvestigator.java @@ -42,7 +42,7 @@ public final class WojekInvestigator extends CardImpl { // At the beginning of your upkeep, investigate once for each opponent who has more cards in hand than you. this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new InvestigateEffect() + new InvestigateEffect(WojekInvestigatorValue.instance) .setText("investigate once for each opponent who has more cards in hand than you"), TargetController.YOU, false ).addHint(WojekInvestigatorValue.getHint())); diff --git a/Mage.Sets/src/mage/cards/w/WordOfCommand.java b/Mage.Sets/src/mage/cards/w/WordOfCommand.java index 5d00c95b116..3aced165f0a 100644 --- a/Mage.Sets/src/mage/cards/w/WordOfCommand.java +++ b/Mage.Sets/src/mage/cards/w/WordOfCommand.java @@ -207,7 +207,7 @@ class WordOfCommandCantActivateEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return !permanent.isLand(game) && permanent.getControllerId().equals(this.targetPointer.getFirst(game, source)); + return !permanent.isLand(game) && permanent.getControllerId().equals(this.getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage.Sets/src/mage/cards/w/WordsOfWar.java b/Mage.Sets/src/mage/cards/w/WordsOfWar.java index f29222e1b04..3b1841f72ec 100644 --- a/Mage.Sets/src/mage/cards/w/WordsOfWar.java +++ b/Mage.Sets/src/mage/cards/w/WordsOfWar.java @@ -63,14 +63,14 @@ class WordsOfWarEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.damage(2, source.getSourceId(), source, game); this.used = true; discard(); return true; } - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(2, source.getSourceId(), source, game, false, true); this.used = true; diff --git a/Mage.Sets/src/mage/cards/w/WorldheartPhoenix.java b/Mage.Sets/src/mage/cards/w/WorldheartPhoenix.java index f5a2525d015..88977f95521 100644 --- a/Mage.Sets/src/mage/cards/w/WorldheartPhoenix.java +++ b/Mage.Sets/src/mage/cards/w/WorldheartPhoenix.java @@ -1,23 +1,23 @@ - package mage.cards.w; import mage.MageIdentifier; import mage.MageInt; +import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.EntersBattlefieldEffect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCounterEnteringCreatureEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; import mage.players.Player; +import mage.watchers.Watcher; import java.util.UUID; @@ -40,8 +40,7 @@ public final class WorldheartPhoenix extends CardImpl { // If you do, it enters the battlefield with two +1/+1 counters on it. Ability ability = new SimpleStaticAbility(Zone.ALL, new WorldheartPhoenixPlayEffect()) .setIdentifier(MageIdentifier.WorldheartPhoenixAlternateCast); - ability.addEffect(new EntersBattlefieldEffect(new WorldheartPhoenixEntersBattlefieldEffect(), - "If you do, it enters the battlefield with two +1/+1 counters on it")); + ability.addWatcher(new WorldheartPhoenixWatcher()); this.addAbility(ability); } @@ -54,80 +53,65 @@ public final class WorldheartPhoenix extends CardImpl { public WorldheartPhoenix copy() { return new WorldheartPhoenix(this); } +} - class WorldheartPhoenixPlayEffect extends AsThoughEffectImpl { - - public WorldheartPhoenixPlayEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may cast {this} from your graveyard by paying {W}{U}{B}{R}{G} rather than paying its mana cost"; - } - - private WorldheartPhoenixPlayEffect(final WorldheartPhoenixPlayEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public WorldheartPhoenixPlayEffect copy() { - return new WorldheartPhoenixPlayEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (sourceId.equals(source.getSourceId()) && source.isControlledBy(affectedControllerId)) { - if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { - Player player = game.getPlayer(affectedControllerId); - if (player != null) { - player.setCastSourceIdWithAlternateMana( - sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), null, - MageIdentifier.WorldheartPhoenixAlternateCast - ); - return true; - } - } - } - return false; - } +class WorldheartPhoenixPlayEffect extends AsThoughEffectImpl { + WorldheartPhoenixPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may cast {this} from your graveyard by paying {W}{U}{B}{R}{G} rather than paying its mana cost. " + + "If you do, it enters the battlefield with two +1/+1 counters on it"; } - class WorldheartPhoenixEntersBattlefieldEffect extends OneShotEffect { + private WorldheartPhoenixPlayEffect(final WorldheartPhoenixPlayEffect effect) { + super(effect); + } - public WorldheartPhoenixEntersBattlefieldEffect() { - super(Outcome.BoostCreature); - staticText = "If you do, it enters the battlefield with two +1/+1 counters on it"; - } + @Override + public boolean apply(Game game, Ability source) { + return true; + } - private WorldheartPhoenixEntersBattlefieldEffect(final WorldheartPhoenixEntersBattlefieldEffect effect) { - super(effect); - } + @Override + public WorldheartPhoenixPlayEffect copy() { + return new WorldheartPhoenixPlayEffect(this); + } - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanentEntering(source.getSourceId()); - if (permanent != null) { - SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY); - if (spellAbility != null - && spellAbility.getSourceId().equals(source.getSourceId()) - && permanent.getZoneChangeCounter(game) == spellAbility.getSourceObjectZoneChangeCounter()) { - // TODO: No perfect solution because there could be other effects that allow to cast the card for this mana cost - if (spellAbility.getManaCosts().getText().equals("{W}{U}{B}{R}{G}")) { - permanent.addCounters(CounterType.P1P1.createInstance(2), source.getControllerId(), source, game); - } + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (sourceId.equals(source.getSourceId()) && source.isControlledBy(affectedControllerId)) { + if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + Player player = game.getPlayer(affectedControllerId); + if (player != null) { + player.setCastSourceIdWithAlternateMana( + sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), null, + MageIdentifier.WorldheartPhoenixAlternateCast + ); + return true; } } - return true; } - - @Override - public WorldheartPhoenixEntersBattlefieldEffect copy() { - return new WorldheartPhoenixEntersBattlefieldEffect(this); - } - + return false; } } + +class WorldheartPhoenixWatcher extends Watcher { + + WorldheartPhoenixWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) + && event.hasApprovingIdentifier(MageIdentifier.WorldheartPhoenixAlternateCast)) { + Spell target = game.getSpell(event.getTargetId()); + if (target != null) { + game.getState().addEffect(new AddCounterEnteringCreatureEffect(new MageObjectReference(target.getCard(), game), + CounterType.P1P1.createInstance(2), Outcome.BoostCreature), + target.getSpellAbility()); + } + } + } +} diff --git a/Mage.Sets/src/mage/cards/w/WorldsoulsRage.java b/Mage.Sets/src/mage/cards/w/WorldsoulsRage.java new file mode 100644 index 00000000000..e13a332c8f1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WorldsoulsRage.java @@ -0,0 +1,105 @@ +package mage.cards.w; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterLandCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetAnyTarget; + +/** + * + * @author DominionSpy + */ +public final class WorldsoulsRage extends CardImpl { + + public WorldsoulsRage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{G}"); + + // Worldsoul's Rage deals X damage to any target. Put up to X land cards from your hand and/or graveyard onto the battlefield tapped. + this.getSpellAbility().addEffect(new DamageTargetEffect(ManacostVariableValue.REGULAR)); + this.getSpellAbility().addEffect(new WorldsoulsRageEffect()); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + } + + private WorldsoulsRage(final WorldsoulsRage card) { + super(card); + } + + @Override + public WorldsoulsRage copy() { + return new WorldsoulsRage(this); + } +} + +class WorldsoulsRageEffect extends OneShotEffect { + + WorldsoulsRageEffect() { + super(Outcome.PutLandInPlay); + staticText = "put up to X land cards from your hand and/or graveyard onto the battlefield tapped."; + } + + private WorldsoulsRageEffect(final WorldsoulsRageEffect effect) { + super(effect); + } + + @Override + public WorldsoulsRageEffect copy() { + return new WorldsoulsRageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + FilterCard filter = new FilterLandCard(); + + Cards landCards = new CardsImpl(); + landCards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game)); + landCards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game)); + if (landCards.isEmpty()) { + return false; + } + + int maxTargets = source.getManaCostsToPay().getX(); + if (maxTargets == 0) { + return false; + } + + TargetCard target = new TargetCard(0, maxTargets, Zone.ALL, filter); + target.withNotTarget(true); + controller.chooseTarget(outcome, landCards, target, source, game); + + Set chosenCards = target.getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (chosenCards.isEmpty()) { + return false; + } + + controller.moveCards(chosenCards, Zone.BATTLEFIELD, source, game, true, false, false, null); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WormfangBehemoth.java b/Mage.Sets/src/mage/cards/w/WormfangBehemoth.java index 4511da5417c..310c255b6cf 100644 --- a/Mage.Sets/src/mage/cards/w/WormfangBehemoth.java +++ b/Mage.Sets/src/mage/cards/w/WormfangBehemoth.java @@ -37,7 +37,7 @@ public final class WormfangBehemoth extends CardImpl { // When Wormfang Behemoth leaves the battlefield, return the exiled cards to their owner's hand. this.addAbility(new LeavesBattlefieldTriggeredAbility( - new ReturnFromExileForSourceEffect(Zone.HAND).withText(true, false), false + new ReturnFromExileForSourceEffect(Zone.HAND).withText(true, false, false), false )); } diff --git a/Mage.Sets/src/mage/cards/w/WrackWithMadness.java b/Mage.Sets/src/mage/cards/w/WrackWithMadness.java index 6e2c86faf4f..339cd3b15ec 100644 --- a/Mage.Sets/src/mage/cards/w/WrackWithMadness.java +++ b/Mage.Sets/src/mage/cards/w/WrackWithMadness.java @@ -55,7 +55,7 @@ class WrackWithMadnessEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.damage(permanent.getPower().getValue(), permanent.getId(), source, game, false, true); return true; diff --git a/Mage.Sets/src/mage/cards/w/WrapInVigor.java b/Mage.Sets/src/mage/cards/w/WrapInVigor.java index 5bc7cbd5778..7d9ad11fe8d 100644 --- a/Mage.Sets/src/mage/cards/w/WrapInVigor.java +++ b/Mage.Sets/src/mage/cards/w/WrapInVigor.java @@ -1,4 +1,3 @@ - package mage.cards.w; import java.util.UUID; @@ -6,7 +5,7 @@ import mage.abilities.effects.common.RegenerateAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; /** * @@ -18,7 +17,7 @@ public final class WrapInVigor extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); // Regenerate each creature you control. - this.getSpellAbility().addEffect(new RegenerateAllEffect(new FilterControlledCreaturePermanent())); + this.getSpellAbility().addEffect(new RegenerateAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE)); } private WrapInVigor(final WrapInVigor card) { diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java index 6a4386bf545..b52afcba949 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java @@ -102,7 +102,7 @@ class WrathfulRaptorsTriggeredAbility extends TriggeredAbilityImpl { class WrathfulRaptorsEffect extends OneShotEffect { WrathfulRaptorsEffect() { - super(Outcome.Benefit); + super(Outcome.Damage); } private WrathfulRaptorsEffect(final WrathfulRaptorsEffect effect) { diff --git a/Mage.Sets/src/mage/cards/w/WrongTurn.java b/Mage.Sets/src/mage/cards/w/WrongTurn.java index fd4b1457c2f..80d8f72470e 100644 --- a/Mage.Sets/src/mage/cards/w/WrongTurn.java +++ b/Mage.Sets/src/mage/cards/w/WrongTurn.java @@ -1,17 +1,11 @@ package mage.cards.w; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.common.TargetPlayerGainControlTargetPermanentEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetOpponent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -24,7 +18,7 @@ public final class WrongTurn extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}"); // Target opponent gains control of target creature. - this.getSpellAbility().addEffect(new WrongTurnEffect()); + this.getSpellAbility().addEffect(new TargetPlayerGainControlTargetPermanentEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } @@ -38,28 +32,3 @@ public final class WrongTurn extends CardImpl { return new WrongTurn(this); } } - -class WrongTurnEffect extends OneShotEffect { - - WrongTurnEffect() { - super(Outcome.Benefit); - staticText = "target opponent gains control of target creature"; - } - - private WrongTurnEffect(final WrongTurnEffect effect) { - super(effect); - } - - @Override - public WrongTurnEffect copy() { - return new WrongTurnEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - game.addEffect(new GainControlTargetEffect( - Duration.Custom, true, source.getFirstTarget() - ).setTargetPointer(new FixedTarget(source.getTargets().get(1).getFirstTarget(), game)), source); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/x/XathridGorgon.java b/Mage.Sets/src/mage/cards/x/XathridGorgon.java index db718fbb4e4..297ab23082f 100644 --- a/Mage.Sets/src/mage/cards/x/XathridGorgon.java +++ b/Mage.Sets/src/mage/cards/x/XathridGorgon.java @@ -6,7 +6,6 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; import mage.abilities.effects.common.continuous.BecomesColorTargetEffect; @@ -43,16 +42,17 @@ public final class XathridGorgon extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // {2}{B}, {tap}: Put a petrification counter on target creature. It gains defender and becomes a colorless artifact in addition to its other types. Its activated abilities can't be activated. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.PETRIFICATION.createInstance()), new ManaCostsImpl<>("{2}{B}")); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, + new AddCountersTargetEffect(CounterType.PETRIFICATION.createInstance()), + new ManaCostsImpl<>("{2}{B}")); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent()); - Effect effect = new GainAbilityTargetEffect(DefenderAbility.getInstance(), Duration.Custom); - effect.setText("It gains defender"); - ability.addEffect(effect); - effect = new AddCardTypeTargetEffect(Duration.Custom, CardType.ARTIFACT); - effect.setText("and becomes a colorless"); - ability.addEffect(effect); - ability.addEffect(new BecomesColorTargetEffect(ObjectColor.COLORLESS, Duration.Custom, " artifact in addition to its other types")); + ability.addEffect(new GainAbilityTargetEffect(DefenderAbility.getInstance(), Duration.Custom) + .setText("It gains defender")); + ability.addEffect(new AddCardTypeTargetEffect(Duration.Custom, CardType.ARTIFACT) + .setText("and becomes a colorless")); + ability.addEffect(new BecomesColorTargetEffect(ObjectColor.COLORLESS, Duration.Custom) + .setText(" artifact in addition to its other types")); ability.addEffect(new XathridGorgonCantActivateEffect()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/x/XenagosGodOfRevels.java b/Mage.Sets/src/mage/cards/x/XenagosGodOfRevels.java index ee852642dc3..554a1e8583d 100644 --- a/Mage.Sets/src/mage/cards/x/XenagosGodOfRevels.java +++ b/Mage.Sets/src/mage/cards/x/XenagosGodOfRevels.java @@ -89,7 +89,7 @@ class XenagosGodOfRevelsEffect extends OneShotEffect { int power = targetCreature.getPower().getValue(); game.addEffect(new BoostTargetEffect( power, power, Duration.EndOfTurn - ).setTargetPointer(this.getTargetPointer()), source); + ).setTargetPointer(this.getTargetPointer().copy()), source); return false; } } diff --git a/Mage.Sets/src/mage/cards/x/XenicPoltergeist.java b/Mage.Sets/src/mage/cards/x/XenicPoltergeist.java index 87b520e1b87..9cd5b7d202c 100644 --- a/Mage.Sets/src/mage/cards/x/XenicPoltergeist.java +++ b/Mage.Sets/src/mage/cards/x/XenicPoltergeist.java @@ -95,7 +95,7 @@ class XenicPoltergeistEffect extends ContinuousEffectImpl { switch (layer) { case TypeChangingEffects_4: if (sublayer == SubLayer.NA) { - UUID permanentId = targetPointer.getFirst(game, source); + UUID permanentId = getTargetPointer().getFirst(game, source); Permanent permanent = game.getPermanentOrLKIBattlefield(permanentId); if (permanent != null) { if (!permanent.isArtifact(game)) { @@ -110,7 +110,7 @@ class XenicPoltergeistEffect extends ContinuousEffectImpl { case PTChangingEffects_7: if (sublayer == SubLayer.SetPT_7b) { - UUID permanentId = targetPointer.getFirst(game, source); + UUID permanentId = getTargetPointer().getFirst(game, source); Permanent permanent = game.getPermanentOrLKIBattlefield(permanentId); if (permanent != null) { int manaCost = permanent.getManaValue(); diff --git a/Mage.Sets/src/mage/cards/x/XiahouDunTheOneEyed.java b/Mage.Sets/src/mage/cards/x/XiahouDunTheOneEyed.java index 2794a03b014..960f837c52b 100644 --- a/Mage.Sets/src/mage/cards/x/XiahouDunTheOneEyed.java +++ b/Mage.Sets/src/mage/cards/x/XiahouDunTheOneEyed.java @@ -26,7 +26,7 @@ import mage.target.common.TargetCardInYourGraveyard; */ public final class XiahouDunTheOneEyed extends CardImpl { - private static final FilterCard filter = new FilterCard("a black card"); + private static final FilterCard filter = new FilterCard("black card"); static { filter.add(new ColorPredicate(ObjectColor.BLACK)); diff --git a/Mage.Sets/src/mage/cards/x/XolatoyacTheSmilingFlood.java b/Mage.Sets/src/mage/cards/x/XolatoyacTheSmilingFlood.java index f00163fecd7..9e2747c268f 100644 --- a/Mage.Sets/src/mage/cards/x/XolatoyacTheSmilingFlood.java +++ b/Mage.Sets/src/mage/cards/x/XolatoyacTheSmilingFlood.java @@ -83,7 +83,7 @@ class XolatoyacTheSmilingFloodEffect extends BecomesBasicLandTargetEffect { @Override public boolean apply(Game game, Ability source) { - Permanent land = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent land = game.getPermanent(this.getTargetPointer().getFirst(game, source)); if (land == null || land.getCounters(game).getCount(CounterType.FLOOD) < 1) { discard(); return false; diff --git a/Mage.Sets/src/mage/cards/y/YamabushisStorm.java b/Mage.Sets/src/mage/cards/y/YamabushisStorm.java index 92ec6080b13..ed4eab7a636 100644 --- a/Mage.Sets/src/mage/cards/y/YamabushisStorm.java +++ b/Mage.Sets/src/mage/cards/y/YamabushisStorm.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.cards.y; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java b/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java new file mode 100644 index 00000000000..d5d4f46f30d --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YarusRoarOfTheOldGods.java @@ -0,0 +1,118 @@ +package mage.cards.y; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.card.FaceDownPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author DominionSpy + */ +public final class YarusRoarOfTheOldGods extends CardImpl { + + private static final FilterCreaturePermanent filter = + new FilterCreaturePermanent("a face-down creature you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(FaceDownPredicate.instance); + } + + public YarusRoarOfTheOldGods(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CENTAUR); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Other creatures you control have haste. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES, true))); + // Whenever one or more face-down creatures you control deal combat damage to a player, draw a card. + this.addAbility(new DealCombatDamageControlledTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter) + .setTriggerPhrase("Whenever one or more face-down creatures you control deal combat damage to a player, ")); + // Whenever a face-down creature you control dies, return it to the battlefield face down under its owner's control if it's a permanent card, then turn it face up. + this.addAbility(new DiesCreatureTriggeredAbility( + new YarusRoarOfTheOldGodsEffect(), false, filter, true)); + } + + private YarusRoarOfTheOldGods(final YarusRoarOfTheOldGods card) { + super(card); + } + + @Override + public YarusRoarOfTheOldGods copy() { + return new YarusRoarOfTheOldGods(this); + } +} + +class YarusRoarOfTheOldGodsEffect extends OneShotEffect { + + YarusRoarOfTheOldGodsEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "return it to the battlefield face down under its owner's control " + + "if it's a permanent card, then turn it face up"; + } + + private YarusRoarOfTheOldGodsEffect(final YarusRoarOfTheOldGodsEffect effect) { + super(effect); + } + + @Override + public YarusRoarOfTheOldGodsEffect copy() { + return new YarusRoarOfTheOldGodsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (controller == null || card == null || !card.isPermanent(game)) { + return false; + } + + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); + MageObjectReference mor = new MageObjectReference(card.getId(), + card.getZoneChangeCounter(game) + 1, game); + game.addEffect(new BecomesFaceDownCreatureEffect( + null, mor, Duration.Custom, + BecomesFaceDownCreatureEffect.FaceDownType.MANUAL), newSource); + controller.moveCards(card, Zone.BATTLEFIELD, source, game, false, true, true, null); + + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null) { + permanent.turnFaceUp(source, game, source.getControllerId()); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/y/YasminKhan.java b/Mage.Sets/src/mage/cards/y/YasminKhan.java index 519b47c937c..da57de39a92 100644 --- a/Mage.Sets/src/mage/cards/y/YasminKhan.java +++ b/Mage.Sets/src/mage/cards/y/YasminKhan.java @@ -31,7 +31,7 @@ public final class YasminKhan extends CardImpl { // {T}: Exile the top card of your library. Until your next end step, you may play it. this.addAbility(new SimpleActivatedAbility(new ExileTopXMayPlayUntilEffect( 1, Duration.UntilYourNextEndStep - ), new TapSourceCost())); + ).withTextOptions("it", false), new TapSourceCost())); // Doctor's companion this.addAbility(DoctorsCompanionAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java b/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java index 97b778d2c9c..3f6f27eeb33 100644 --- a/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java +++ b/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java @@ -111,7 +111,7 @@ class YedoraGraveGardenerContinuousEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target == null || !target.isFaceDown(game)) { discard(); return false; diff --git a/Mage.Sets/src/mage/cards/z/ZadasCommando.java b/Mage.Sets/src/mage/cards/z/ZadasCommando.java index 9b287129bc2..4b0b6183d7f 100644 --- a/Mage.Sets/src/mage/cards/z/ZadasCommando.java +++ b/Mage.Sets/src/mage/cards/z/ZadasCommando.java @@ -1,55 +1,36 @@ - package mage.cards.z; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetOpponentOrPlaneswalker; +import java.util.UUID; + /** * * @author fireshoes */ public final class ZadasCommando extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("an untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public ZadasCommando(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); this.subtype.add(SubType.GOBLIN, SubType.ARCHER, SubType.ALLY); - //this.subtype.add(SubType.GOBLIN); - //this.subtype.add(SubType.ARCHER); - //this.subtype.add(SubType.ALLY); this.power = new MageInt(2); this.toughness = new MageInt(1); - // First Strike + // First strike this.addAbility(FirstStrikeAbility.getInstance()); // Cohort — {T}, Tap an untapped Ally you control: Zada's Commando deals 1 damage to target opponent. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new TapSourceCost()); - ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); + Ability ability = new CohortAbility(new DamageTargetEffect(1)); ability.addTarget(new TargetOpponentOrPlaneswalker()); - ability.setAbilityWord(AbilityWord.COHORT); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java index 424f7df9480..2af062d23a0 100644 --- a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java +++ b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java @@ -78,7 +78,7 @@ class ZaraRenegadeRecruiterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || player == null || player.getHand().isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java b/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java index d432142f053..dc895db40b8 100644 --- a/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java +++ b/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java @@ -1,26 +1,23 @@ - package mage.cards.z; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.PermanentsYouOwnThatOpponentsControlCount; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.TargetPlayerGainControlTargetPermanentEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author andyfries @@ -44,7 +41,7 @@ public final class ZedruuTheGreathearted extends CardImpl { this.addAbility(ability); // {R}{W}{U}: Target opponent gains control of target permanent you control. - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ZedruuTheGreatheartedEffect(), new ManaCostsImpl<>("{U}{R}{W}")); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TargetPlayerGainControlTargetPermanentEffect(), new ManaCostsImpl<>("{U}{R}{W}")); ability.addTarget(new TargetOpponent()); ability.addTarget(new TargetControlledPermanent()); this.addAbility(ability); @@ -59,40 +56,4 @@ public final class ZedruuTheGreathearted extends CardImpl { return new ZedruuTheGreathearted(this); } - class ZedruuTheGreatheartedEffect extends ContinuousEffectImpl { - - private MageObjectReference targetPermanentReference; - - public ZedruuTheGreatheartedEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - this.staticText = "Target opponent gains control of target permanent you control"; - } - - private ZedruuTheGreatheartedEffect(final ZedruuTheGreatheartedEffect effect) { - super(effect); - this.targetPermanentReference = effect.targetPermanentReference; - } - - @Override - public ZedruuTheGreatheartedEffect copy() { - return new ZedruuTheGreatheartedEffect(this); - } - - @Override - public void init(Ability source, Game game) { - super.init(source, game); - targetPermanentReference = new MageObjectReference(source.getTargets().get(1).getFirstTarget(), game); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = targetPermanentReference.getPermanent(game); - if (permanent != null) { - return permanent.changeControllerId(source.getFirstTarget(), game, source); - } else { - discard(); - } - return false; - } - } } diff --git a/Mage.Sets/src/mage/cards/z/ZhalfirinLancer.java b/Mage.Sets/src/mage/cards/z/ZhalfirinLancer.java index 2ad4681db46..b78a77b953a 100644 --- a/Mage.Sets/src/mage/cards/z/ZhalfirinLancer.java +++ b/Mage.Sets/src/mage/cards/z/ZhalfirinLancer.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import java.util.UUID; @@ -22,7 +22,7 @@ import java.util.UUID; */ public final class ZhalfirinLancer extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.KNIGHT, "another Knight"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.KNIGHT, "another Knight"); static { filter.add(AnotherPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/z/ZhulodokVoidGorger.java b/Mage.Sets/src/mage/cards/z/ZhulodokVoidGorger.java index 3e91d8eff35..35f7e9f65e6 100644 --- a/Mage.Sets/src/mage/cards/z/ZhulodokVoidGorger.java +++ b/Mage.Sets/src/mage/cards/z/ZhulodokVoidGorger.java @@ -8,7 +8,7 @@ import mage.abilities.keyword.CascadeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.card.CastFromZonePredicate; import mage.filter.predicate.mageobject.ColorlessPredicate; import mage.filter.predicate.mageobject.ManaValuePredicate; @@ -20,7 +20,7 @@ import java.util.UUID; */ public final class ZhulodokVoidGorger extends CardImpl { - private static final FilterCard filter = new FilterCard("colorless spells you cast from your hand with mana value 7 or greater"); + private static final FilterNonlandCard filter = new FilterNonlandCard("colorless spells you cast from your hand with mana value 7 or greater"); static { filter.add(ColorlessPredicate.instance); diff --git a/Mage.Sets/src/mage/cards/z/ZombieMob.java b/Mage.Sets/src/mage/cards/z/ZombieMob.java index b7b33d4c9b8..521362014e0 100644 --- a/Mage.Sets/src/mage/cards/z/ZombieMob.java +++ b/Mage.Sets/src/mage/cards/z/ZombieMob.java @@ -1,27 +1,27 @@ - package mage.cards.z; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; -/** - * - * @author tcontis - */ +import java.util.UUID; +/** + * @author xenohedron + */ public final class ZombieMob extends CardImpl { public ZombieMob(UUID ownerId, CardSetInfo setInfo) { @@ -31,8 +31,12 @@ public final class ZombieMob extends CardImpl { this.toughness = new MageInt(0); // Zombie Mob enters the battlefield with a +1/+1 counter on it for each creature card in your graveyard. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(0), new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE), false + ), "with a +1/+1 counter on it for each creature card in your graveyard")); + // When Zombie Mob enters the battlefield, exile all creature cards from your graveyard. - this.addAbility(new EntersBattlefieldAbility(new ZombieMobEffect(), "with a +1/+1 counter on it for each creature card in your graveyard. When {this} enters the battlefield, exile all creature cards from your graveyard.")); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ZombieMobExileEffect())); } @@ -46,42 +50,30 @@ public final class ZombieMob extends CardImpl { } } -class ZombieMobEffect extends OneShotEffect { +class ZombieMobExileEffect extends OneShotEffect { - private static final FilterCard filter = new FilterCard(); - static { - filter.add(CardType.CREATURE.getPredicate()); + ZombieMobExileEffect() { + super(Outcome.Benefit); + staticText = "exile all creature cards from your graveyard"; } - public ZombieMobEffect() { - super(Outcome.BoostCreature); - staticText = "{this} enters the battlefield with a +1/+1 counter on it for each creature card in your graveyard. When {this} enters the battlefield, exile all creature cards from your graveyard."; - } - - private ZombieMobEffect(final ZombieMobEffect effect) { + private ZombieMobExileEffect(final ZombieMobExileEffect effect) { super(effect); } + @Override + public ZombieMobExileEffect copy() { + return new ZombieMobExileEffect(this); + } + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanentEntering(source.getSourceId()); - if (permanent != null && controller != null) { - int amount = 0; - amount += controller.getGraveyard().count(filter, game); - if (amount > 0) { - permanent.addCounters(CounterType.P1P1.createInstance(amount), source.getControllerId(), source, game); - } - Cards cards = new CardsImpl(controller.getGraveyard().getCards(filter, game)); - controller.moveCards(cards, Zone.EXILED, source, game); - return true; + if (controller == null) { + return false; } - return false; + Cards cards = new CardsImpl(controller.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game)); + controller.moveCards(cards, Zone.EXILED, source, game); + return true; } - - @Override - public ZombieMobEffect copy() { - return new ZombieMobEffect(this); - } - -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/z/ZopandrelHungerDominus.java b/Mage.Sets/src/mage/cards/z/ZopandrelHungerDominus.java index 89b9f26897d..cb161239f8f 100644 --- a/Mage.Sets/src/mage/cards/z/ZopandrelHungerDominus.java +++ b/Mage.Sets/src/mage/cards/z/ZopandrelHungerDominus.java @@ -15,12 +15,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledPermanent; -import mage.target.common.TargetSacrifice; import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -71,9 +70,7 @@ public final class ZopandrelHungerDominus extends CardImpl { class ZopandrelHungerDominusEffect extends OneShotEffect { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - - public ZopandrelHungerDominusEffect() { + ZopandrelHungerDominusEffect() { super(Outcome.BoostCreature); staticText = "double the power and toughness of each creature you control until end of turn"; } @@ -89,7 +86,7 @@ class ZopandrelHungerDominusEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game)) { ContinuousEffect effect = new BoostTargetEffect(permanent.getPower().getValue(), permanent.getToughness().getValue()); effect.setTargetPointer(new FixedTarget(permanent, game)); game.addEffect(effect, source); diff --git a/Mage.Sets/src/mage/cards/z/ZulaportChainmage.java b/Mage.Sets/src/mage/cards/z/ZulaportChainmage.java index 624f3c75e66..1b2582ac161 100644 --- a/Mage.Sets/src/mage/cards/z/ZulaportChainmage.java +++ b/Mage.Sets/src/mage/cards/z/ZulaportChainmage.java @@ -1,36 +1,23 @@ - package mage.cards.z; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.Ability; +import mage.abilities.abilityword.CohortAbility; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author LevelX2 */ public final class ZulaportChainmage extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped Ally you control"); - - static { - filter.add(SubType.ALLY.getPredicate()); - filter.add(TappedPredicate.UNTAPPED); - } - public ZulaportChainmage(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); this.subtype.add(SubType.HUMAN, SubType.SHAMAN, SubType.ALLY); @@ -38,11 +25,7 @@ public final class ZulaportChainmage extends CardImpl { this.toughness = new MageInt(2); // Cohort — {T}, Tap an untapped Ally you control: Target opponent loses 2 life. - SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new LoseLifeTargetEffect(2), - new TapSourceCost()); - ability.setAbilityWord(AbilityWord.COHORT); - ability.addCost(new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false))); + Ability ability = new CohortAbility(new LoseLifeTargetEffect(2)); ability.addTarget(new TargetOpponent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/ArenaBeginnerSet.java b/Mage.Sets/src/mage/sets/ArenaBeginnerSet.java new file mode 100644 index 00000000000..19cbcc39ff9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/ArenaBeginnerSet.java @@ -0,0 +1,144 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/anb + */ +public class ArenaBeginnerSet extends ExpansionSet { + + private static final ArenaBeginnerSet instance = new ArenaBeginnerSet(); + + public static ArenaBeginnerSet getInstance() { + return instance; + } + + private ArenaBeginnerSet() { + super("Arena Beginner Set", "ANB", ExpansionSet.buildDate(2020, 8, 13), SetType.MAGIC_ONLINE); + this.hasBoosters = false; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Affectionate Indrik", 89, Rarity.UNCOMMON, mage.cards.a.AffectionateIndrik.class)); + cards.add(new SetCardInfo("Air Elemental", 23, Rarity.UNCOMMON, mage.cards.a.AirElemental.class)); + cards.add(new SetCardInfo("Angelic Guardian", 2, Rarity.RARE, mage.cards.a.AngelicGuardian.class)); + cards.add(new SetCardInfo("Angelic Reward", 3, Rarity.UNCOMMON, mage.cards.a.AngelicReward.class)); + cards.add(new SetCardInfo("Angel of Vitality", 1, Rarity.UNCOMMON, mage.cards.a.AngelOfVitality.class)); + cards.add(new SetCardInfo("Arcane Signet", 117, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Armored Whirl Turtle", 24, Rarity.COMMON, mage.cards.a.ArmoredWhirlTurtle.class)); + cards.add(new SetCardInfo("Bad Deal", 45, Rarity.UNCOMMON, mage.cards.b.BadDeal.class)); + cards.add(new SetCardInfo("Baloth Packhunter", 90, Rarity.COMMON, mage.cards.b.BalothPackhunter.class)); + cards.add(new SetCardInfo("Bombard", 67, Rarity.COMMON, mage.cards.b.Bombard.class)); + cards.add(new SetCardInfo("Bond of Discipline", 4, Rarity.UNCOMMON, mage.cards.b.BondOfDiscipline.class)); + cards.add(new SetCardInfo("Burn Bright", 68, Rarity.COMMON, mage.cards.b.BurnBright.class)); + cards.add(new SetCardInfo("Charging Badger", 91, Rarity.COMMON, mage.cards.c.ChargingBadger.class)); + cards.add(new SetCardInfo("Charmed Stray", 5, Rarity.COMMON, mage.cards.c.CharmedStray.class)); + cards.add(new SetCardInfo("Cloudkin Seer", 25, Rarity.COMMON, mage.cards.c.CloudkinSeer.class)); + cards.add(new SetCardInfo("Colossal Majesty", 92, Rarity.UNCOMMON, mage.cards.c.ColossalMajesty.class)); + cards.add(new SetCardInfo("Command Tower", 118, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Compound Fracture", 46, Rarity.COMMON, mage.cards.c.CompoundFracture.class)); + cards.add(new SetCardInfo("Confront the Assault", 6, Rarity.UNCOMMON, mage.cards.c.ConfrontTheAssault.class)); + cards.add(new SetCardInfo("Coral Merfolk", 26, Rarity.COMMON, mage.cards.c.CoralMerfolk.class)); + cards.add(new SetCardInfo("Cruel Cut", 47, Rarity.COMMON, mage.cards.c.CruelCut.class)); + cards.add(new SetCardInfo("Demon of Loathing", 48, Rarity.RARE, mage.cards.d.DemonOfLoathing.class)); + cards.add(new SetCardInfo("Epic Proportions", 93, Rarity.RARE, mage.cards.e.EpicProportions.class)); + cards.add(new SetCardInfo("Eternal Thirst", 49, Rarity.UNCOMMON, mage.cards.e.EternalThirst.class)); + cards.add(new SetCardInfo("Evolving Wilds", 111, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Fearless Halberdier", 69, Rarity.COMMON, mage.cards.f.FearlessHalberdier.class)); + cards.add(new SetCardInfo("Fencing Ace", 7, Rarity.COMMON, mage.cards.f.FencingAce.class)); + cards.add(new SetCardInfo("Feral Roar", 94, Rarity.COMMON, mage.cards.f.FeralRoar.class)); + cards.add(new SetCardInfo("Forest", 112, Rarity.LAND, mage.cards.basiclands.Forest.class)); + cards.add(new SetCardInfo("Frilled Sea Serpent", 27, Rarity.COMMON, mage.cards.f.FrilledSeaSerpent.class)); + cards.add(new SetCardInfo("Generous Stray", 95, Rarity.COMMON, mage.cards.g.GenerousStray.class)); + cards.add(new SetCardInfo("Gigantosaurus", 96, Rarity.RARE, mage.cards.g.Gigantosaurus.class)); + cards.add(new SetCardInfo("Glint", 28, Rarity.COMMON, mage.cards.g.Glint.class)); + cards.add(new SetCardInfo("Goblin Gang Leader", 70, Rarity.UNCOMMON, mage.cards.g.GoblinGangLeader.class)); + cards.add(new SetCardInfo("Goblin Gathering", 71, Rarity.COMMON, mage.cards.g.GoblinGathering.class)); + cards.add(new SetCardInfo("Goblin Trashmaster", 72, Rarity.RARE, mage.cards.g.GoblinTrashmaster.class)); + cards.add(new SetCardInfo("Goblin Tunneler", 73, Rarity.COMMON, mage.cards.g.GoblinTunneler.class)); + cards.add(new SetCardInfo("Goring Ceratops", 8, Rarity.RARE, mage.cards.g.GoringCeratops.class)); + cards.add(new SetCardInfo("Greenwood Sentinel", 97, Rarity.COMMON, mage.cards.g.GreenwoodSentinel.class)); + cards.add(new SetCardInfo("Hallowed Priest", 9, Rarity.UNCOMMON, mage.cards.h.HallowedPriest.class)); + cards.add(new SetCardInfo("Hurloon Minotaur", 74, Rarity.COMMON, mage.cards.h.HurloonMinotaur.class)); + cards.add(new SetCardInfo("Ilysian Caryatid", 98, Rarity.COMMON, mage.cards.i.IlysianCaryatid.class)); + cards.add(new SetCardInfo("Immortal Phoenix", 75, Rarity.RARE, mage.cards.i.ImmortalPhoenix.class)); + cards.add(new SetCardInfo("Impassioned Orator", 10, Rarity.COMMON, mage.cards.i.ImpassionedOrator.class)); + cards.add(new SetCardInfo("Inescapable Blaze", 76, Rarity.UNCOMMON, mage.cards.i.InescapableBlaze.class)); + cards.add(new SetCardInfo("Inspiring Commander", 11, Rarity.RARE, mage.cards.i.InspiringCommander.class)); + cards.add(new SetCardInfo("Island", 113, Rarity.LAND, mage.cards.basiclands.Island.class)); + cards.add(new SetCardInfo("Jungle Delver", 99, Rarity.COMMON, mage.cards.j.JungleDelver.class)); + cards.add(new SetCardInfo("Knight's Pledge", 12, Rarity.COMMON, mage.cards.k.KnightsPledge.class)); + cards.add(new SetCardInfo("Krovikan Scoundrel", 50, Rarity.COMMON, mage.cards.k.KrovikanScoundrel.class)); + cards.add(new SetCardInfo("Leonin Warleader", 13, Rarity.RARE, mage.cards.l.LeoninWarleader.class)); + cards.add(new SetCardInfo("Loxodon Line Breaker", 14, Rarity.COMMON, mage.cards.l.LoxodonLineBreaker.class)); + cards.add(new SetCardInfo("Malakir Cullblade", 51, Rarity.UNCOMMON, mage.cards.m.MalakirCullblade.class)); + cards.add(new SetCardInfo("Maniacal Rage", 77, Rarity.COMMON, mage.cards.m.ManiacalRage.class)); + cards.add(new SetCardInfo("Mardu Outrider", 52, Rarity.RARE, mage.cards.m.MarduOutrider.class)); + cards.add(new SetCardInfo("Molten Ravager", 78, Rarity.COMMON, mage.cards.m.MoltenRavager.class)); + cards.add(new SetCardInfo("Moorland Inquisitor", 15, Rarity.COMMON, mage.cards.m.MoorlandInquisitor.class)); + cards.add(new SetCardInfo("Mountain", 114, Rarity.LAND, mage.cards.basiclands.Mountain.class)); + cards.add(new SetCardInfo("Murder", 53, Rarity.UNCOMMON, mage.cards.m.Murder.class)); + cards.add(new SetCardInfo("Nest Robber", 79, Rarity.COMMON, mage.cards.n.NestRobber.class)); + cards.add(new SetCardInfo("Nightmare", 54, Rarity.RARE, mage.cards.n.Nightmare.class)); + cards.add(new SetCardInfo("Nimble Pilferer", 55, Rarity.COMMON, mage.cards.n.NimblePilferer.class)); + cards.add(new SetCardInfo("Octoprophet", 29, Rarity.COMMON, mage.cards.o.Octoprophet.class)); + cards.add(new SetCardInfo("Ogre Battledriver", 80, Rarity.RARE, mage.cards.o.OgreBattledriver.class)); + cards.add(new SetCardInfo("Overflowing Insight", 30, Rarity.RARE, mage.cards.o.OverflowingInsight.class)); + cards.add(new SetCardInfo("Pacifism", 16, Rarity.COMMON, mage.cards.p.Pacifism.class)); + cards.add(new SetCardInfo("Plains", 115, Rarity.LAND, mage.cards.basiclands.Plains.class)); + cards.add(new SetCardInfo("Prized Unicorn", 100, Rarity.UNCOMMON, mage.cards.p.PrizedUnicorn.class)); + cards.add(new SetCardInfo("Rabid Bite", 101, Rarity.COMMON, mage.cards.r.RabidBite.class)); + cards.add(new SetCardInfo("Raging Goblin", 81, Rarity.COMMON, mage.cards.r.RagingGoblin.class)); + cards.add(new SetCardInfo("Raid Bombardment", 82, Rarity.UNCOMMON, mage.cards.r.RaidBombardment.class)); + cards.add(new SetCardInfo("Raise Dead", 56, Rarity.COMMON, mage.cards.r.RaiseDead.class)); + cards.add(new SetCardInfo("Rampaging Brontodon", 102, Rarity.RARE, mage.cards.r.RampagingBrontodon.class)); + cards.add(new SetCardInfo("Reduce to Ashes", 83, Rarity.COMMON, mage.cards.r.ReduceToAshes.class)); + cards.add(new SetCardInfo("Riddlemaster Sphinx", 31, Rarity.RARE, mage.cards.r.RiddlemasterSphinx.class)); + cards.add(new SetCardInfo("Rise from the Grave", "56b", Rarity.UNCOMMON, mage.cards.r.RiseFromTheGrave.class)); + cards.add(new SetCardInfo("River's Favor", 32, Rarity.COMMON, mage.cards.r.RiversFavor.class)); + cards.add(new SetCardInfo("Rumbling Baloth", 103, Rarity.COMMON, mage.cards.r.RumblingBaloth.class)); + cards.add(new SetCardInfo("Sanctuary Cat", 17, Rarity.COMMON, mage.cards.s.SanctuaryCat.class)); + cards.add(new SetCardInfo("Sanitarium Skeleton", 57, Rarity.COMMON, mage.cards.s.SanitariumSkeleton.class)); + cards.add(new SetCardInfo("Savage Gorger", 58, Rarity.COMMON, mage.cards.s.SavageGorger.class)); + cards.add(new SetCardInfo("Scathe Zombies", 59, Rarity.COMMON, mage.cards.s.ScatheZombies.class)); + cards.add(new SetCardInfo("Sengir Vampire", 60, Rarity.UNCOMMON, mage.cards.s.SengirVampire.class)); + cards.add(new SetCardInfo("Sentinel Spider", 104, Rarity.UNCOMMON, mage.cards.s.SentinelSpider.class)); + cards.add(new SetCardInfo("Serra Angel", 18, Rarity.UNCOMMON, mage.cards.s.SerraAngel.class)); + cards.add(new SetCardInfo("Shock", 84, Rarity.COMMON, mage.cards.s.Shock.class)); + cards.add(new SetCardInfo("Shorecomber Crab", "32a", Rarity.COMMON, mage.cards.s.ShorecomberCrab.class)); + cards.add(new SetCardInfo("Shrine Keeper", 19, Rarity.COMMON, mage.cards.s.ShrineKeeper.class)); + cards.add(new SetCardInfo("Siege Dragon", 85, Rarity.RARE, mage.cards.s.SiegeDragon.class)); + cards.add(new SetCardInfo("Skeleton Archer", 61, Rarity.COMMON, mage.cards.s.SkeletonArcher.class)); + cards.add(new SetCardInfo("Sleep", 33, Rarity.UNCOMMON, mage.cards.s.Sleep.class)); + cards.add(new SetCardInfo("Soulblade Djinn", 34, Rarity.RARE, mage.cards.s.SoulbladeDjinn.class)); + cards.add(new SetCardInfo("Soulhunter Rakshasa", 62, Rarity.RARE, mage.cards.s.SoulhunterRakshasa.class)); + cards.add(new SetCardInfo("Soulmender", 20, Rarity.COMMON, mage.cards.s.Soulmender.class)); + cards.add(new SetCardInfo("Spiritual Guardian", 21, Rarity.COMMON, mage.cards.s.SpiritualGuardian.class)); + cards.add(new SetCardInfo("Stony Strength", 105, Rarity.COMMON, mage.cards.s.StonyStrength.class)); + cards.add(new SetCardInfo("Storm Strike", 86, Rarity.COMMON, mage.cards.s.StormStrike.class)); + cards.add(new SetCardInfo("Swamp", 116, Rarity.LAND, mage.cards.basiclands.Swamp.class)); + cards.add(new SetCardInfo("Sworn Guardian", 35, Rarity.COMMON, mage.cards.s.SwornGuardian.class)); + cards.add(new SetCardInfo("Tactical Advantage", 22, Rarity.COMMON, mage.cards.t.TacticalAdvantage.class)); + cards.add(new SetCardInfo("Tin Street Cadet", 87, Rarity.COMMON, mage.cards.t.TinStreetCadet.class)); + cards.add(new SetCardInfo("Titanic Growth", 106, Rarity.COMMON, mage.cards.t.TitanicGrowth.class)); + cards.add(new SetCardInfo("Treetop Warden", 107, Rarity.COMMON, mage.cards.t.TreetopWarden.class)); + cards.add(new SetCardInfo("Typhoid Rats", 63, Rarity.COMMON, mage.cards.t.TyphoidRats.class)); + cards.add(new SetCardInfo("Unlikely Aid", 64, Rarity.COMMON, mage.cards.u.UnlikelyAid.class)); + cards.add(new SetCardInfo("Unsummon", 36, Rarity.COMMON, mage.cards.u.Unsummon.class)); + cards.add(new SetCardInfo("Vampire Opportunist", 65, Rarity.COMMON, mage.cards.v.VampireOpportunist.class)); + cards.add(new SetCardInfo("Volcanic Dragon", 88, Rarity.UNCOMMON, mage.cards.v.VolcanicDragon.class)); + cards.add(new SetCardInfo("Wall of Runes", 37, Rarity.COMMON, mage.cards.w.WallOfRunes.class)); + cards.add(new SetCardInfo("Warden of Evos Isle", 38, Rarity.UNCOMMON, mage.cards.w.WardenOfEvosIsle.class)); + cards.add(new SetCardInfo("Waterkin Shaman", 39, Rarity.COMMON, mage.cards.w.WaterkinShaman.class)); + cards.add(new SetCardInfo("Waterknot", 40, Rarity.COMMON, mage.cards.w.Waterknot.class)); + cards.add(new SetCardInfo("Wildwood Patrol", 108, Rarity.COMMON, mage.cards.w.WildwoodPatrol.class)); + cards.add(new SetCardInfo("Windreader Sphinx", 41, Rarity.RARE, mage.cards.w.WindreaderSphinx.class)); + cards.add(new SetCardInfo("Windstorm Drake", 42, Rarity.UNCOMMON, mage.cards.w.WindstormDrake.class)); + cards.add(new SetCardInfo("Winged Words", 43, Rarity.COMMON, mage.cards.w.WingedWords.class)); + cards.add(new SetCardInfo("Witch's Familiar", 66, Rarity.COMMON, mage.cards.w.WitchsFamiliar.class)); + cards.add(new SetCardInfo("Woodland Mystic", 109, Rarity.COMMON, mage.cards.w.WoodlandMystic.class)); + cards.add(new SetCardInfo("World Shaper", 110, Rarity.RARE, mage.cards.w.WorldShaper.class)); + cards.add(new SetCardInfo("Zephyr Gull", 44, Rarity.COMMON, mage.cards.z.ZephyrGull.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/ArenaNewPlayerExperienceExtras.java b/Mage.Sets/src/mage/sets/ArenaNewPlayerExperienceExtras.java index e11bd24b53a..f870c059b39 100644 --- a/Mage.Sets/src/mage/sets/ArenaNewPlayerExperienceExtras.java +++ b/Mage.Sets/src/mage/sets/ArenaNewPlayerExperienceExtras.java @@ -20,35 +20,31 @@ public final class ArenaNewPlayerExperienceExtras extends ExpansionSet { this.hasBoosters = false; this.hasBasicLands = true; - cards.add(new SetCardInfo("Altar's Reap", 24, Rarity.COMMON, mage.cards.a.AltarsReap.class)); cards.add(new SetCardInfo("Ambition's Cost", 25, Rarity.UNCOMMON, mage.cards.a.AmbitionsCost.class)); - cards.add(new SetCardInfo("Blinding Radiance", 2, Rarity.UNCOMMON, mage.cards.b.BlindingRadiance.class)); - cards.add(new SetCardInfo("Chaos Maw", 36, Rarity.RARE, mage.cards.c.ChaosMaw.class)); cards.add(new SetCardInfo("Cruel Cut", 26, Rarity.COMMON, mage.cards.c.CruelCut.class)); cards.add(new SetCardInfo("Divination", 14, Rarity.COMMON, mage.cards.d.Divination.class)); cards.add(new SetCardInfo("Doublecast", 37, Rarity.UNCOMMON, mage.cards.d.Doublecast.class)); cards.add(new SetCardInfo("Forest", 55, Rarity.LAND, mage.cards.basiclands.Forest.class)); - cards.add(new SetCardInfo("Goblin Bruiser", 39, Rarity.UNCOMMON, mage.cards.g.GoblinBruiser.class)); cards.add(new SetCardInfo("Goblin Gang Leader", 40, Rarity.UNCOMMON, mage.cards.g.GoblinGangLeader.class)); - cards.add(new SetCardInfo("Goblin Grenade", 41, Rarity.UNCOMMON, mage.cards.g.GoblinGrenade.class)); cards.add(new SetCardInfo("Island", 52, Rarity.LAND, mage.cards.basiclands.Island.class)); cards.add(new SetCardInfo("Loxodon Line Breaker", 7, Rarity.COMMON, mage.cards.l.LoxodonLineBreaker.class)); cards.add(new SetCardInfo("Miasmic Mummy", 29, Rarity.COMMON, mage.cards.m.MiasmicMummy.class)); cards.add(new SetCardInfo("Mountain", 54, Rarity.LAND, mage.cards.basiclands.Mountain.class)); cards.add(new SetCardInfo("Nimble Pilferer", 31, Rarity.COMMON, mage.cards.n.NimblePilferer.class)); - cards.add(new SetCardInfo("Ogre Painbringer", 42, Rarity.RARE, mage.cards.o.OgrePainbringer.class)); cards.add(new SetCardInfo("Overflowing Insight", 16, Rarity.MYTHIC, mage.cards.o.OverflowingInsight.class)); cards.add(new SetCardInfo("Plains", 51, Rarity.LAND, mage.cards.basiclands.Plains.class)); - cards.add(new SetCardInfo("Renegade Demon", 33, Rarity.COMMON, mage.cards.r.RenegadeDemon.class)); cards.add(new SetCardInfo("Rise from the Grave", 34, Rarity.UNCOMMON, mage.cards.r.RiseFromTheGrave.class)); cards.add(new SetCardInfo("Rumbling Baloth", 47, Rarity.COMMON, mage.cards.r.RumblingBaloth.class)); - cards.add(new SetCardInfo("Seismic Rupture", 44, Rarity.UNCOMMON, mage.cards.s.SeismicRupture.class)); - cards.add(new SetCardInfo("Shorecomber Crab", 20, Rarity.COMMON, mage.cards.s.ShorecomberCrab.class)); cards.add(new SetCardInfo("Soulhunter Rakshasa", 35, Rarity.RARE, mage.cards.s.SoulhunterRakshasa.class)); cards.add(new SetCardInfo("Swamp", 53, Rarity.LAND, mage.cards.basiclands.Swamp.class)); cards.add(new SetCardInfo("Take Vengeance", 13, Rarity.COMMON, mage.cards.t.TakeVengeance.class)); - cards.add(new SetCardInfo("Titanic Pelagosaur", 19, Rarity.UNCOMMON, mage.cards.t.TitanicPelagosaur.class)); cards.add(new SetCardInfo("Volcanic Dragon", 45, Rarity.UNCOMMON, mage.cards.v.VolcanicDragon.class)); cards.add(new SetCardInfo("Waterknot", 22, Rarity.COMMON, mage.cards.w.Waterknot.class)); + + // cards removed entirely - no longer exist on scryfall at all. + // cards.add(new SetCardInfo("Blinding Radiance", 2, Rarity.UNCOMMON, mage.cards.b.BlindingRadiance.class)); + // cards.add(new SetCardInfo("Goblin Bruiser", 39, Rarity.UNCOMMON, mage.cards.g.GoblinBruiser.class)); + // cards.add(new SetCardInfo("Ogre Painbringer", 42, Rarity.RARE, mage.cards.o.OgrePainbringer.class)); + // cards.add(new SetCardInfo("Titanic Pelagosaur", 19, Rarity.UNCOMMON, mage.cards.t.TitanicPelagosaur.class)); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java new file mode 100644 index 00000000000..fbd4a2aaf02 --- /dev/null +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -0,0 +1,31 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class AssassinsCreed extends ExpansionSet { + + private static final AssassinsCreed instance = new AssassinsCreed(); + + public static AssassinsCreed getInstance() { + return instance; + } + + private AssassinsCreed() { + super("Assassin's Creed", "ACR", ExpansionSet.buildDate(2024, 7, 5), SetType.EXPANSION); + this.blockName = "Assassin's Creed"; // for sorting in GUI + this.hasBasicLands = false; + this.hasBoosters = false; + + cards.add(new SetCardInfo("Cover of Darkness", 89, Rarity.RARE, mage.cards.c.CoverOfDarkness.class)); + cards.add(new SetCardInfo("Eivor, Battle-Ready", 274, Rarity.MYTHIC, mage.cards.e.EivorBattleReady.class)); + cards.add(new SetCardInfo("Ezio, Blade of Vengeance", 275, Rarity.MYTHIC, mage.cards.e.EzioBladeOfVengeance.class)); + cards.add(new SetCardInfo("Haystack", 175, Rarity.UNCOMMON, mage.cards.h.Haystack.class)); + cards.add(new SetCardInfo("Sword of Feast and Famine", 99, Rarity.MYTHIC, mage.cards.s.SwordOfFeastAndFamine.class)); + cards.add(new SetCardInfo("Temporal Trespass", 86, Rarity.MYTHIC, mage.cards.t.TemporalTrespass.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/Battlebond.java b/Mage.Sets/src/mage/sets/Battlebond.java index fc2fa54e79b..42e1784f8f9 100644 --- a/Mage.Sets/src/mage/sets/Battlebond.java +++ b/Mage.Sets/src/mage/sets/Battlebond.java @@ -358,7 +358,7 @@ public final class Battlebond extends ExpansionSet { //Check if the pack already contains a partner pair if (partnerAllowed) { //Added card always replaces an uncommon card - Card card = CardRepository.instance.findCardWithPreferredSetAndNumber(partnerName, sourceCard.getExpansionSetCode(), null).getCard(); + Card card = CardRepository.instance.findCardWithPreferredSetAndNumber(partnerName, sourceCard.getExpansionSetCode(), null).createCard(); if (i < max) { booster.add(card); } else { diff --git a/Mage.Sets/src/mage/sets/Bloomburrow.java b/Mage.Sets/src/mage/sets/Bloomburrow.java new file mode 100644 index 00000000000..fa04a6def9f --- /dev/null +++ b/Mage.Sets/src/mage/sets/Bloomburrow.java @@ -0,0 +1,30 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class Bloomburrow extends ExpansionSet { + + private static final Bloomburrow instance = new Bloomburrow(); + + public static Bloomburrow getInstance() { + return instance; + } + + private Bloomburrow() { + super("Bloomburrow", "BLB", ExpansionSet.buildDate(2024, 8, 2), SetType.EXPANSION); + this.blockName = "Bloomburrow"; // for sorting in GUI + this.hasBasicLands = true; + this.hasBoosters = false; // temporary + + cards.add(new SetCardInfo("Bria, Riptide Rogue", 379, Rarity.MYTHIC, mage.cards.b.BriaRiptideRogue.class)); + cards.add(new SetCardInfo("Byrke, Long Ear of the Law", 380, Rarity.MYTHIC, mage.cards.b.ByrkeLongEarOfTheLaw.class)); + cards.add(new SetCardInfo("Lumra, Bellow of the Woods", 183, Rarity.MYTHIC, mage.cards.l.LumraBellowOfTheWoods.class)); + cards.add(new SetCardInfo("Mabel, Heir to Cragflame", 224, Rarity.RARE, mage.cards.m.MabelHeirToCragflame.class)); + cards.add(new SetCardInfo("Mountain", 274, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 3ea6e9af711..0e7a70792eb 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -72,6 +72,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Desolate Lighthouse", 271, Rarity.RARE, mage.cards.d.DesolateLighthouse.class)); cards.add(new SetCardInfo("Dinosaurs on a Spaceship", 122, Rarity.RARE, mage.cards.d.DinosaursOnASpaceship.class)); cards.add(new SetCardInfo("Displaced Dinosaurs", 100, Rarity.UNCOMMON, mage.cards.d.DisplacedDinosaurs.class)); + cards.add(new SetCardInfo("Donna Noble", 82, Rarity.RARE, mage.cards.d.DonnaNoble.class)); cards.add(new SetCardInfo("Dragonskull Summit", 272, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); cards.add(new SetCardInfo("Dreamroot Cascade", 273, Rarity.RARE, mage.cards.d.DreamrootCascade.class)); cards.add(new SetCardInfo("Drowned Catacomb", 274, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); @@ -141,6 +142,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Lunar Hatchling", 141, Rarity.RARE, mage.cards.l.LunarHatchling.class)); cards.add(new SetCardInfo("Madame Vastra", 142, Rarity.RARE, mage.cards.m.MadameVastra.class)); cards.add(new SetCardInfo("Martha Jones", 48, Rarity.RARE, mage.cards.m.MarthaJones.class)); + cards.add(new SetCardInfo("Me, the Immortal", 147, Rarity.RARE, mage.cards.m.MeTheImmortal.class)); cards.add(new SetCardInfo("Memory Worm", 90, Rarity.UNCOMMON, mage.cards.m.MemoryWorm.class)); cards.add(new SetCardInfo("Mind Stone", 244, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); cards.add(new SetCardInfo("Mountain", 202, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); @@ -200,6 +202,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Surge of Brilliance", 57, Rarity.UNCOMMON, mage.cards.s.SurgeOfBrilliance.class)); cards.add(new SetCardInfo("Swamp", 200, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swords to Plowshares", 212, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("TARDIS", 187, Rarity.UNCOMMON, mage.cards.t.TARDIS.class)); cards.add(new SetCardInfo("Talisman of Conviction", 247, Rarity.UNCOMMON, mage.cards.t.TalismanOfConviction.class)); cards.add(new SetCardInfo("Talisman of Creativity", 248, Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class)); cards.add(new SetCardInfo("Talisman of Curiosity", 249, Rarity.UNCOMMON, mage.cards.t.TalismanOfCuriosity.class)); diff --git a/Mage.Sets/src/mage/sets/Fallout.java b/Mage.Sets/src/mage/sets/Fallout.java index 7e27bfb47d9..215c55f79f6 100644 --- a/Mage.Sets/src/mage/sets/Fallout.java +++ b/Mage.Sets/src/mage/sets/Fallout.java @@ -18,24 +18,263 @@ public final class Fallout extends ExpansionSet { private Fallout() { super("Fallout", "PIP", ExpansionSet.buildDate(2024, 3, 8), SetType.SUPPLEMENTAL); + cards.add(new SetCardInfo("Abundant Growth", 194, Rarity.COMMON, mage.cards.a.AbundantGrowth.class)); + cards.add(new SetCardInfo("Agent Frank Horrigan", 89, Rarity.RARE, mage.cards.a.AgentFrankHorrigan.class)); + cards.add(new SetCardInfo("All That Glitters", 155, Rarity.COMMON, mage.cards.a.AllThatGlitters.class)); + cards.add(new SetCardInfo("Almost Perfect", 90, Rarity.RARE, mage.cards.a.AlmostPerfect.class)); cards.add(new SetCardInfo("Alpha Deathclaw", 91, Rarity.RARE, mage.cards.a.AlphaDeathclaw.class)); - cards.add(new SetCardInfo("Arcane Signet", 356, Rarity.UNCOMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Anguished Unmaking", 209, Rarity.RARE, mage.cards.a.AnguishedUnmaking.class)); + cards.add(new SetCardInfo("Arcane Signet", 224, Rarity.UNCOMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Armory Paladin", 93, Rarity.RARE, mage.cards.a.ArmoryPaladin.class)); + cards.add(new SetCardInfo("Ash Barrens", 253, Rarity.COMMON, mage.cards.a.AshBarrens.class)); + cards.add(new SetCardInfo("Assaultron Dominator", 54, Rarity.RARE, mage.cards.a.AssaultronDominator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assaultron Dominator", 384, Rarity.RARE, mage.cards.a.AssaultronDominator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assaultron Dominator", 582, Rarity.RARE, mage.cards.a.AssaultronDominator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assaultron Dominator", 912, Rarity.RARE, mage.cards.a.AssaultronDominator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assemble the Legion", 210, Rarity.RARE, mage.cards.a.AssembleTheLegion.class)); + cards.add(new SetCardInfo("Atomize", 94, Rarity.RARE, mage.cards.a.Atomize.class)); + cards.add(new SetCardInfo("Austere Command", 156, Rarity.RARE, mage.cards.a.AustereCommand.class)); + cards.add(new SetCardInfo("Automated Assembly Line", 10, Rarity.RARE, mage.cards.a.AutomatedAssemblyLine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Automated Assembly Line", 363, Rarity.RARE, mage.cards.a.AutomatedAssemblyLine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Automated Assembly Line", 538, Rarity.RARE, mage.cards.a.AutomatedAssemblyLine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Automated Assembly Line", 891, Rarity.RARE, mage.cards.a.AutomatedAssemblyLine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ayula, Queen Among Bears", 348, Rarity.RARE, mage.cards.a.AyulaQueenAmongBears.class)); + cards.add(new SetCardInfo("Basilisk Collar", 225, Rarity.RARE, mage.cards.b.BasiliskCollar.class)); + cards.add(new SetCardInfo("Bastion of Remembrance", 182, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); + cards.add(new SetCardInfo("Behemoth Sledge", 211, Rarity.UNCOMMON, mage.cards.b.BehemothSledge.class)); + cards.add(new SetCardInfo("Biomass Mutation", 212, Rarity.RARE, mage.cards.b.BiomassMutation.class)); + cards.add(new SetCardInfo("Black Market", 183, Rarity.RARE, mage.cards.b.BlackMarket.class)); + cards.add(new SetCardInfo("Blasphemous Act", 188, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); + cards.add(new SetCardInfo("Bloodforged Battle-Axe", 226, Rarity.RARE, mage.cards.b.BloodforgedBattleAxe.class)); + cards.add(new SetCardInfo("Boomer Scrapper", 95, Rarity.RARE, mage.cards.b.BoomerScrapper.class)); + cards.add(new SetCardInfo("Branching Evolution", 195, Rarity.RARE, mage.cards.b.BranchingEvolution.class)); + cards.add(new SetCardInfo("Brass Knuckles", 227, Rarity.UNCOMMON, mage.cards.b.BrassKnuckles.class)); + cards.add(new SetCardInfo("Break Down", 74, Rarity.UNCOMMON, mage.cards.b.BreakDown.class)); + cards.add(new SetCardInfo("Brotherhood Outcast", 12, Rarity.UNCOMMON, mage.cards.b.BrotherhoodOutcast.class)); + cards.add(new SetCardInfo("Brotherhood Scribe", 13, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brotherhood Scribe", 365, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brotherhood Scribe", 541, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brotherhood Scribe", 893, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Buried Ruin", 254, Rarity.UNCOMMON, mage.cards.b.BuriedRuin.class)); + cards.add(new SetCardInfo("Caesar, Legion's Emperor", 1, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caesar, Legion's Emperor", 339, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caesar, Legion's Emperor", 529, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caesar, Legion's Emperor", 867, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caesar, Legion's Emperor", 1064, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canopy Vista", 255, Rarity.RARE, mage.cards.c.CanopyVista.class)); + cards.add(new SetCardInfo("Canyon Slough", 256, Rarity.RARE, mage.cards.c.CanyonSlough.class)); + cards.add(new SetCardInfo("Captain of the Watch", 157, Rarity.RARE, mage.cards.c.CaptainOfTheWatch.class)); + cards.add(new SetCardInfo("Casualties of War", 213, Rarity.RARE, mage.cards.c.CasualtiesOfWar.class)); + cards.add(new SetCardInfo("Champion's Helm", 228, Rarity.RARE, mage.cards.c.ChampionsHelm.class)); + cards.add(new SetCardInfo("Chaos Warp", 189, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Cinder Glade", 257, Rarity.RARE, mage.cards.c.CinderGlade.class)); + cards.add(new SetCardInfo("Clifftop Retreat", 258, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); + cards.add(new SetCardInfo("Colonel Autumn", 98, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Colonel Autumn", 411, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Colonel Autumn", 626, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Colonel Autumn", 939, Rarity.RARE, mage.cards.c.ColonelAutumn.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Command Tower", 259, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Commander Sofia Daguerre", 15, Rarity.UNCOMMON, mage.cards.c.CommanderSofiaDaguerre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander Sofia Daguerre", 543, Rarity.UNCOMMON, mage.cards.c.CommanderSofiaDaguerre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contagion Clasp", 229, Rarity.UNCOMMON, mage.cards.c.ContagionClasp.class)); + cards.add(new SetCardInfo("Corpsejack Menace", 214, Rarity.UNCOMMON, mage.cards.c.CorpsejackMenace.class)); + cards.add(new SetCardInfo("Crimson Caravaneer", 56, Rarity.UNCOMMON, mage.cards.c.CrimsonCaravaneer.class)); cards.add(new SetCardInfo("Crucible of Worlds", 357, Rarity.MYTHIC, mage.cards.c.CrucibleOfWorlds.class)); + cards.add(new SetCardInfo("Crush Contraband", 158, Rarity.UNCOMMON, mage.cards.c.CrushContraband.class)); + cards.add(new SetCardInfo("Cultivate", 196, Rarity.UNCOMMON, mage.cards.c.Cultivate.class)); + cards.add(new SetCardInfo("Darkwater Catacombs", 260, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); + cards.add(new SetCardInfo("Deadly Dispute", 184, Rarity.COMMON, mage.cards.d.DeadlyDispute.class)); + cards.add(new SetCardInfo("Desolate Mire", 146, Rarity.RARE, mage.cards.d.DesolateMire.class)); + cards.add(new SetCardInfo("Dispatch", 159, Rarity.UNCOMMON, mage.cards.d.Dispatch.class)); cards.add(new SetCardInfo("Dr. Madison Li", 3, Rarity.MYTHIC, mage.cards.d.DrMadisonLi.class)); - cards.add(new SetCardInfo("Forest", 325, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dragonskull Summit", 261, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); + cards.add(new SetCardInfo("Drowned Catacomb", 262, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); + cards.add(new SetCardInfo("Entrapment Maneuver", 160, Rarity.RARE, mage.cards.e.EntrapmentManeuver.class)); + cards.add(new SetCardInfo("Everflowing Chalice", 230, Rarity.UNCOMMON, mage.cards.e.EverflowingChalice.class)); + cards.add(new SetCardInfo("Evolving Wilds", 263, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Exotic Orchard", 264, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Expert-Level Safe", 133, Rarity.UNCOMMON, mage.cards.e.ExpertLevelSafe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Expert-Level Safe", 661, Rarity.UNCOMMON, mage.cards.e.ExpertLevelSafe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explorer's Scope", 231, Rarity.COMMON, mage.cards.e.ExplorersScope.class)); + cards.add(new SetCardInfo("Farewell", 353, Rarity.RARE, mage.cards.f.Farewell.class)); + cards.add(new SetCardInfo("Farseek", 197, Rarity.COMMON, mage.cards.f.Farseek.class)); + cards.add(new SetCardInfo("Ferrous Lake", 148, Rarity.RARE, mage.cards.f.FerrousLake.class)); + cards.add(new SetCardInfo("Fertile Ground", 198, Rarity.COMMON, mage.cards.f.FertileGround.class)); + cards.add(new SetCardInfo("Fervent Charge", 215, Rarity.RARE, mage.cards.f.FerventCharge.class)); + cards.add(new SetCardInfo("Fetid Pools", 265, Rarity.RARE, mage.cards.f.FetidPools.class)); + cards.add(new SetCardInfo("Find // Finality", 216, Rarity.RARE, mage.cards.f.FindFinality.class)); + cards.add(new SetCardInfo("Fireshrieker", 232, Rarity.UNCOMMON, mage.cards.f.Fireshrieker.class)); + cards.add(new SetCardInfo("Forest", 325, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Fraying Sanity", 175, Rarity.RARE, mage.cards.f.FrayingSanity.class)); cards.add(new SetCardInfo("Gary Clone", 16, Rarity.UNCOMMON, mage.cards.g.GaryClone.class)); +// cards.add(new SetCardInfo("Gemrazer", 351, Rarity.RARE, mage.cards.g.Gemrazer.class)); + cards.add(new SetCardInfo("General's Enforcer", 217, Rarity.UNCOMMON, mage.cards.g.GeneralsEnforcer.class)); + cards.add(new SetCardInfo("Glacial Fortress", 266, Rarity.RARE, mage.cards.g.GlacialFortress.class)); + cards.add(new SetCardInfo("Glimmer of Genius", 176, Rarity.UNCOMMON, mage.cards.g.GlimmerOfGenius.class)); + cards.add(new SetCardInfo("Grave Titan", 346, Rarity.MYTHIC, mage.cards.g.GraveTitan.class)); + cards.add(new SetCardInfo("Guardian Project", 199, Rarity.RARE, mage.cards.g.GuardianProject.class)); + cards.add(new SetCardInfo("Hancock, Ghoulish Mayor", 45, Rarity.RARE, mage.cards.h.HancockGhoulishMayor.class)); + cards.add(new SetCardInfo("Hardened Scales", 200, Rarity.RARE, mage.cards.h.HardenedScales.class)); + cards.add(new SetCardInfo("Harmonize", 201, Rarity.UNCOMMON, mage.cards.h.Harmonize.class)); + cards.add(new SetCardInfo("Heroic Intervention", 202, Rarity.RARE, mage.cards.h.HeroicIntervention.class)); + cards.add(new SetCardInfo("Heroic Reinforcements", 218, Rarity.UNCOMMON, mage.cards.h.HeroicReinforcements.class)); + cards.add(new SetCardInfo("Hinterland Harbor", 267, Rarity.RARE, mage.cards.h.HinterlandHarbor.class)); + cards.add(new SetCardInfo("Hornet Queen", 350, Rarity.RARE, mage.cards.h.HornetQueen.class)); + cards.add(new SetCardInfo("Hour of Reckoning", 161, Rarity.RARE, mage.cards.h.HourOfReckoning.class)); + cards.add(new SetCardInfo("Hullbreaker Horror", 344, Rarity.RARE, mage.cards.h.HullbreakerHorror.class)); + cards.add(new SetCardInfo("Impassioned Orator", 162, Rarity.COMMON, mage.cards.i.ImpassionedOrator.class)); + cards.add(new SetCardInfo("Inexorable Tide", 177, Rarity.RARE, mage.cards.i.InexorableTide.class)); + cards.add(new SetCardInfo("Inspiring Call", 203, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class)); + cards.add(new SetCardInfo("Intangible Virtue", 163, Rarity.UNCOMMON, mage.cards.i.IntangibleVirtue.class)); cards.add(new SetCardInfo("Intelligence Bobblehead", 134, Rarity.UNCOMMON, mage.cards.i.IntelligenceBobblehead.class)); - cards.add(new SetCardInfo("Island", 319, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 323, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Irrigated Farmland", 268, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); + cards.add(new SetCardInfo("Island", 319, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Isolated Chapel", 269, Rarity.RARE, mage.cards.i.IsolatedChapel.class)); + cards.add(new SetCardInfo("Jungle Shrine", 270, Rarity.UNCOMMON, mage.cards.j.JungleShrine.class)); + cards.add(new SetCardInfo("Junktown", 150, Rarity.RARE, mage.cards.j.Junktown.class)); + cards.add(new SetCardInfo("Keeper of the Accord", 164, Rarity.RARE, mage.cards.k.KeeperOfTheAccord.class)); + cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 106, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 415, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 634, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 943, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lethal Scheme", 185, Rarity.RARE, mage.cards.l.LethalScheme.class)); + cards.add(new SetCardInfo("Liberty Prime, Recharged", 5, Rarity.MYTHIC, mage.cards.l.LibertyPrimeRecharged.class)); + cards.add(new SetCardInfo("Lightning Greaves", 233, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); + cards.add(new SetCardInfo("Lord of the Undead", 345, Rarity.RARE, mage.cards.l.LordOfTheUndead.class)); + cards.add(new SetCardInfo("Loyal Apprentice", 190, Rarity.UNCOMMON, mage.cards.l.LoyalApprentice.class)); + cards.add(new SetCardInfo("MacCready, Lamplight Mayor", 108, Rarity.RARE, mage.cards.m.MacCreadyLamplightMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("MacCready, Lamplight Mayor", 417, Rarity.RARE, mage.cards.m.MacCreadyLamplightMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("MacCready, Lamplight Mayor", 636, Rarity.RARE, mage.cards.m.MacCreadyLamplightMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("MacCready, Lamplight Mayor", 945, Rarity.RARE, mage.cards.m.MacCreadyLamplightMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mantle of the Ancients", 165, Rarity.RARE, mage.cards.m.MantleOfTheAncients.class)); + cards.add(new SetCardInfo("Marshal's Anthem", 166, Rarity.RARE, mage.cards.m.MarshalsAnthem.class)); + cards.add(new SetCardInfo("Martial Coup", 167, Rarity.RARE, mage.cards.m.MartialCoup.class)); + cards.add(new SetCardInfo("Masterwork of Ingenuity", 234, Rarity.RARE, mage.cards.m.MasterworkOfIngenuity.class)); + cards.add(new SetCardInfo("Mechanized Production", 178, Rarity.MYTHIC, mage.cards.m.MechanizedProduction.class)); + cards.add(new SetCardInfo("Memorial to Glory", 271, Rarity.UNCOMMON, mage.cards.m.MemorialToGlory.class)); + cards.add(new SetCardInfo("Mind Stone", 235, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); + cards.add(new SetCardInfo("Mister Gutsy", 136, Rarity.RARE, mage.cards.m.MisterGutsy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mister Gutsy", 433, Rarity.RARE, mage.cards.m.MisterGutsy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mister Gutsy", 664, Rarity.RARE, mage.cards.m.MisterGutsy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mister Gutsy", 961, Rarity.RARE, mage.cards.m.MisterGutsy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Morbid Opportunist", 186, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class)); + cards.add(new SetCardInfo("Mortuary Mire", 272, Rarity.COMMON, mage.cards.m.MortuaryMire.class)); + cards.add(new SetCardInfo("Mossfire Valley", 273, Rarity.RARE, mage.cards.m.MossfireValley.class)); + cards.add(new SetCardInfo("Mountain", 323, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mr. House, President and CEO", 7, Rarity.MYTHIC, mage.cards.m.MrHousePresidentAndCEO.class)); + cards.add(new SetCardInfo("Myriad Landscape", 274, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); + cards.add(new SetCardInfo("Mystic Forge", 236, Rarity.RARE, mage.cards.m.MysticForge.class)); + cards.add(new SetCardInfo("Mystic Monastery", 275, Rarity.UNCOMMON, mage.cards.m.MysticMonastery.class)); + cards.add(new SetCardInfo("Nerd Rage", 34, Rarity.UNCOMMON, mage.cards.n.NerdRage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nerd Rage", 562, Rarity.UNCOMMON, mage.cards.n.NerdRage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nesting Grounds", 276, Rarity.RARE, mage.cards.n.NestingGrounds.class)); + cards.add(new SetCardInfo("Nomad Outpost", 277, Rarity.UNCOMMON, mage.cards.n.NomadOutpost.class)); cards.add(new SetCardInfo("Nuka-Cola Vending Machine", 137, Rarity.UNCOMMON, mage.cards.n.NukaColaVendingMachine.class)); - cards.add(new SetCardInfo("Plains", 317, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("One with the Machine", 179, Rarity.RARE, mage.cards.o.OneWithTheMachine.class)); + cards.add(new SetCardInfo("Open the Vaults", 168, Rarity.RARE, mage.cards.o.OpenTheVaults.class)); + cards.add(new SetCardInfo("Opulent Palace", 278, Rarity.UNCOMMON, mage.cards.o.OpulentPalace.class)); + cards.add(new SetCardInfo("Overflowing Basin", 152, Rarity.RARE, mage.cards.o.OverflowingBasin.class)); + cards.add(new SetCardInfo("Panharmonicon", 237, Rarity.RARE, mage.cards.p.Panharmonicon.class)); + cards.add(new SetCardInfo("Path of Ancestry", 279, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); + cards.add(new SetCardInfo("Path to Exile", 169, Rarity.UNCOMMON, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Pitiless Plunderer", 187, Rarity.UNCOMMON, mage.cards.p.PitilessPlunderer.class)); + cards.add(new SetCardInfo("Plains", 317, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Powder Ganger", 65, Rarity.RARE, mage.cards.p.PowderGanger.class)); + cards.add(new SetCardInfo("Prairie Stream", 280, Rarity.RARE, mage.cards.p.PrairieStream.class)); + cards.add(new SetCardInfo("Puresteel Paladin", 170, Rarity.RARE, mage.cards.p.PuresteelPaladin.class)); + cards.add(new SetCardInfo("Putrefy", 219, Rarity.UNCOMMON, mage.cards.p.Putrefy.class)); cards.add(new SetCardInfo("Radstorm", 37, Rarity.RARE, mage.cards.r.Radstorm.class)); - cards.add(new SetCardInfo("Sol Ring", 359, Rarity.MYTHIC, mage.cards.s.SolRing.class)); - cards.add(new SetCardInfo("Swamp", 321, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rampant Growth", 204, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); + cards.add(new SetCardInfo("Rancor", 205, Rarity.UNCOMMON, mage.cards.r.Rancor.class)); + cards.add(new SetCardInfo("Ravages of War", 354, Rarity.MYTHIC, mage.cards.r.RavagesOfWar.class)); + cards.add(new SetCardInfo("Razortide Bridge", 281, Rarity.COMMON, mage.cards.r.RazortideBridge.class)); + cards.add(new SetCardInfo("Red Death, Shipwrecker", 116, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Red Death, Shipwrecker", 426, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Red Death, Shipwrecker", 644, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Red Death, Shipwrecker", 954, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Roadside Reliquary", 282, Rarity.UNCOMMON, mage.cards.r.RoadsideReliquary.class)); + cards.add(new SetCardInfo("Rogue's Passage", 283, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); + cards.add(new SetCardInfo("Rootbound Crag", 284, Rarity.RARE, mage.cards.r.RootboundCrag.class)); + cards.add(new SetCardInfo("Ruinous Ultimatum", 220, Rarity.RARE, mage.cards.r.RuinousUltimatum.class)); + cards.add(new SetCardInfo("Rustvale Bridge", 285, Rarity.COMMON, mage.cards.r.RustvaleBridge.class)); + cards.add(new SetCardInfo("Scattered Groves", 286, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); + cards.add(new SetCardInfo("Scavenger Grounds", 287, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); + cards.add(new SetCardInfo("Secure the Wastes", 171, Rarity.RARE, mage.cards.s.SecureTheWastes.class)); + cards.add(new SetCardInfo("Shadowblood Ridge", 288, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); + cards.add(new SetCardInfo("Sheltered Thicket", 289, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); + cards.add(new SetCardInfo("Sierra, Nuka's Biggest Fan", 25, Rarity.RARE, mage.cards.s.SierraNukasBiggestFan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sierra, Nuka's Biggest Fan", 372, Rarity.RARE, mage.cards.s.SierraNukasBiggestFan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sierra, Nuka's Biggest Fan", 553, Rarity.RARE, mage.cards.s.SierraNukasBiggestFan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sierra, Nuka's Biggest Fan", 900, Rarity.RARE, mage.cards.s.SierraNukasBiggestFan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Silver Shroud Costume", 142, Rarity.UNCOMMON, mage.cards.s.SilverShroudCostume.class)); + cards.add(new SetCardInfo("Silverbluff Bridge", 290, Rarity.COMMON, mage.cards.s.SilverbluffBridge.class)); + cards.add(new SetCardInfo("Single Combat", 172, Rarity.RARE, mage.cards.s.SingleCombat.class)); + cards.add(new SetCardInfo("Skullclamp", 238, Rarity.UNCOMMON, mage.cards.s.Skullclamp.class)); + cards.add(new SetCardInfo("Skycloud Expanse", 291, Rarity.RARE, mage.cards.s.SkycloudExpanse.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 292, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Sol Ring", 239, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); + cards.add(new SetCardInfo("Solemn Simulacrum", 240, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); + cards.add(new SetCardInfo("Spire of Industry", 293, Rarity.RARE, mage.cards.s.SpireOfIndustry.class)); + cards.add(new SetCardInfo("Squirrel Nest", 206, Rarity.UNCOMMON, mage.cards.s.SquirrelNest.class)); + cards.add(new SetCardInfo("Steel Overseer", 241, Rarity.RARE, mage.cards.s.SteelOverseer.class)); + cards.add(new SetCardInfo("Sticky Fingers", 191, Rarity.COMMON, mage.cards.s.StickyFingers.class)); + cards.add(new SetCardInfo("Stolen Strategy", 192, Rarity.RARE, mage.cards.s.StolenStrategy.class)); + cards.add(new SetCardInfo("Sulfur Falls", 294, Rarity.RARE, mage.cards.s.SulfurFalls.class)); + cards.add(new SetCardInfo("Sungrass Prairie", 295, Rarity.RARE, mage.cards.s.SungrassPrairie.class)); + cards.add(new SetCardInfo("Sunken Hollow", 296, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Sunpetal Grove", 297, Rarity.RARE, mage.cards.s.SunpetalGrove.class)); + cards.add(new SetCardInfo("Sunscorched Divide", 153, Rarity.RARE, mage.cards.s.SunscorchedDivide.class)); + cards.add(new SetCardInfo("Super Mutant Scavenger", 85, Rarity.UNCOMMON, mage.cards.s.SuperMutantScavenger.class)); + cards.add(new SetCardInfo("Swamp", 321, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swiftfoot Boots", 242, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Swords to Plowshares", 173, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Tainted Field", 298, Rarity.UNCOMMON, mage.cards.t.TaintedField.class)); + cards.add(new SetCardInfo("Tainted Isle", 299, Rarity.UNCOMMON, mage.cards.t.TaintedIsle.class)); + cards.add(new SetCardInfo("Tainted Peak", 300, Rarity.UNCOMMON, mage.cards.t.TaintedPeak.class)); + cards.add(new SetCardInfo("Tainted Wood", 301, Rarity.UNCOMMON, mage.cards.t.TaintedWood.class)); + cards.add(new SetCardInfo("Talisman of Conviction", 243, Rarity.UNCOMMON, mage.cards.t.TalismanOfConviction.class)); + cards.add(new SetCardInfo("Talisman of Creativity", 244, Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class)); + cards.add(new SetCardInfo("Talisman of Curiosity", 245, Rarity.UNCOMMON, mage.cards.t.TalismanOfCuriosity.class)); + cards.add(new SetCardInfo("Talisman of Dominance", 246, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); + cards.add(new SetCardInfo("Talisman of Hierarchy", 247, Rarity.UNCOMMON, mage.cards.t.TalismanOfHierarchy.class)); + cards.add(new SetCardInfo("Talisman of Indulgence", 248, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class)); + cards.add(new SetCardInfo("Talisman of Progress", 249, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class)); + cards.add(new SetCardInfo("Talisman of Resilience", 250, Rarity.UNCOMMON, mage.cards.t.TalismanOfResilience.class)); + cards.add(new SetCardInfo("Tarmogoyf", 349, Rarity.MYTHIC, mage.cards.t.Tarmogoyf.class)); + cards.add(new SetCardInfo("Temple of Abandon", 302, Rarity.RARE, mage.cards.t.TempleOfAbandon.class)); + cards.add(new SetCardInfo("Temple of Deceit", 303, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); + cards.add(new SetCardInfo("Temple of Enlightenment", 304, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); + cards.add(new SetCardInfo("Temple of Epiphany", 305, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class)); + cards.add(new SetCardInfo("Temple of Malady", 306, Rarity.RARE, mage.cards.t.TempleOfMalady.class)); + cards.add(new SetCardInfo("Temple of Malice", 307, Rarity.RARE, mage.cards.t.TempleOfMalice.class)); + cards.add(new SetCardInfo("Temple of Mystery", 308, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); + cards.add(new SetCardInfo("Temple of Plenty", 309, Rarity.RARE, mage.cards.t.TempleOfPlenty.class)); + cards.add(new SetCardInfo("Temple of Silence", 310, Rarity.RARE, mage.cards.t.TempleOfSilence.class)); + cards.add(new SetCardInfo("Temple of Triumph", 312, Rarity.RARE, mage.cards.t.TempleOfTriumph.class)); + cards.add(new SetCardInfo("Temple of the False God", 311, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); + cards.add(new SetCardInfo("Terramorphic Expanse", 313, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); + cards.add(new SetCardInfo("Thirst for Knowledge", 180, Rarity.UNCOMMON, mage.cards.t.ThirstForKnowledge.class)); + cards.add(new SetCardInfo("Thought Vessel", 251, Rarity.COMMON, mage.cards.t.ThoughtVessel.class)); + cards.add(new SetCardInfo("Tireless Tracker", 207, Rarity.RARE, mage.cards.t.TirelessTracker.class)); + cards.add(new SetCardInfo("Treasure Vault", 314, Rarity.RARE, mage.cards.t.TreasureVault.class)); + cards.add(new SetCardInfo("Unexpected Windfall", 193, Rarity.COMMON, mage.cards.u.UnexpectedWindfall.class)); cards.add(new SetCardInfo("V.A.T.S.", 50, Rarity.RARE, mage.cards.v.VATS.class)); + cards.add(new SetCardInfo("Valorous Stance", 174, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class)); + cards.add(new SetCardInfo("Vandalblast", 355, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class)); cards.add(new SetCardInfo("Vault 101: Birthday Party", 28, Rarity.RARE, mage.cards.v.Vault101BirthdayParty.class)); + cards.add(new SetCardInfo("Vigor", 347, Rarity.RARE, mage.cards.v.Vigor.class)); + cards.add(new SetCardInfo("Viridescent Bog", 154, Rarity.RARE, mage.cards.v.ViridescentBog.class)); + cards.add(new SetCardInfo("Wake the Past", 221, Rarity.RARE, mage.cards.w.WakeThePast.class)); + cards.add(new SetCardInfo("Walking Ballista", 352, Rarity.RARE, mage.cards.w.WalkingBallista.class)); + cards.add(new SetCardInfo("War Room", 1068, Rarity.RARE, mage.cards.w.WarRoom.class)); cards.add(new SetCardInfo("Wasteland", 361, Rarity.RARE, mage.cards.w.Wasteland.class)); + cards.add(new SetCardInfo("Watchful Radstag", 87, Rarity.RARE, mage.cards.w.WatchfulRadstag.class)); + cards.add(new SetCardInfo("Wayfarer's Bauble", 252, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); + cards.add(new SetCardInfo("Wear // Tear", 222, Rarity.UNCOMMON, mage.cards.w.WearTear.class)); + cards.add(new SetCardInfo("Whirler Rogue", 181, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class)); + cards.add(new SetCardInfo("Wild Growth", 208, Rarity.COMMON, mage.cards.w.WildGrowth.class)); + cards.add(new SetCardInfo("Windbrisk Heights", 315, Rarity.RARE, mage.cards.w.WindbriskHeights.class)); + cards.add(new SetCardInfo("Winding Constrictor", 223, Rarity.UNCOMMON, mage.cards.w.WindingConstrictor.class)); + cards.add(new SetCardInfo("Woodland Cemetery", 316, Rarity.RARE, mage.cards.w.WoodlandCemetery.class)); } } diff --git a/Mage.Sets/src/mage/sets/HeroesOfTheRealm2017.java b/Mage.Sets/src/mage/sets/HeroesOfTheRealm2017.java new file mode 100644 index 00000000000..a712e7615aa --- /dev/null +++ b/Mage.Sets/src/mage/sets/HeroesOfTheRealm2017.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/ph17 + * @author PurpleCrowbar + */ +public class HeroesOfTheRealm2017 extends ExpansionSet { + + private static final HeroesOfTheRealm2017 instance = new HeroesOfTheRealm2017(); + + public static HeroesOfTheRealm2017 getInstance() { + return instance; + } + + private HeroesOfTheRealm2017() { + super("Heroes of the Realm 2017", "PH17", ExpansionSet.buildDate(2018, 8, 1), SetType.JOKESET); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Inzerva, Master of Insights", 2, Rarity.MYTHIC, mage.cards.i.InzervaMasterOfInsights.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index f0afc39af85..515fc93367e 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -34,6 +34,7 @@ public final class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Altar of Bone", 281, Rarity.RARE, mage.cards.a.AltarOfBone.class)); cards.add(new SetCardInfo("Anarchy", 170, Rarity.UNCOMMON, mage.cards.a.Anarchy.class)); cards.add(new SetCardInfo("Arctic Foxes", 2, Rarity.COMMON, mage.cards.a.ArcticFoxes.class)); + cards.add(new SetCardInfo("Arcum's Sleigh", 309, Rarity.UNCOMMON, mage.cards.a.ArcumsSleigh.class)); cards.add(new SetCardInfo("Arenson's Aura", 3, Rarity.COMMON, mage.cards.a.ArensonsAura.class)); cards.add(new SetCardInfo("Armor of Faith", 4, Rarity.COMMON, mage.cards.a.ArmorOfFaith.class)); cards.add(new SetCardInfo("Arnjlot's Ascent", 57, Rarity.COMMON, mage.cards.a.ArnjlotsAscent.class)); diff --git a/Mage.Sets/src/mage/sets/JurassicWorldCollection.java b/Mage.Sets/src/mage/sets/JurassicWorldCollection.java index 5e1fdacb67d..f3ab88c1dc4 100644 --- a/Mage.Sets/src/mage/sets/JurassicWorldCollection.java +++ b/Mage.Sets/src/mage/sets/JurassicWorldCollection.java @@ -22,6 +22,7 @@ public final class JurassicWorldCollection extends ExpansionSet { cards.add(new SetCardInfo("Command Tower", 26, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Command Tower", "26b", Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dino DNA", 20, Rarity.RARE, mage.cards.d.DinoDNA.class)); cards.add(new SetCardInfo("Don't Move", 1, Rarity.RARE, mage.cards.d.DontMove.class)); cards.add(new SetCardInfo("Ellie and Alan, Paleontologists", 10, Rarity.RARE, mage.cards.e.EllieAndAlanPaleontologists.class)); cards.add(new SetCardInfo("Forest", 25, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index 9d34e243915..a80735ac120 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -26,6 +26,7 @@ public final class Mirage extends ExpansionSet { this.ratioBoosterMythic = 0; cards.add(new SetCardInfo("Abyssal Hunter", 103, Rarity.RARE, mage.cards.a.AbyssalHunter.class)); + cards.add(new SetCardInfo("Acidic Dagger", 291, Rarity.RARE, mage.cards.a.AcidicDagger.class)); cards.add(new SetCardInfo("Afiya Grove", 205, Rarity.RARE, mage.cards.a.AfiyaGrove.class)); cards.add(new SetCardInfo("Afterlife", 1, Rarity.UNCOMMON, mage.cards.a.Afterlife.class)); cards.add(new SetCardInfo("Agility", 154, Rarity.COMMON, mage.cards.a.Agility.class)); @@ -329,6 +330,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Thirst", 99, Rarity.COMMON, mage.cards.t.Thirst.class)); cards.add(new SetCardInfo("Tidal Wave", 100, Rarity.UNCOMMON, mage.cards.t.TidalWave.class)); cards.add(new SetCardInfo("Tombstone Stairwell", 149, Rarity.RARE, mage.cards.t.TombstoneStairwell.class)); + cards.add(new SetCardInfo("Torrent of Lava", 199, Rarity.RARE, mage.cards.t.TorrentOfLava.class)); cards.add(new SetCardInfo("Tranquil Domain", 245, Rarity.COMMON, mage.cards.t.TranquilDomain.class)); cards.add(new SetCardInfo("Tropical Storm", 246, Rarity.UNCOMMON, mage.cards.t.TropicalStorm.class)); cards.add(new SetCardInfo("Uktabi Faerie", 247, Rarity.COMMON, mage.cards.u.UktabiFaerie.class)); diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java new file mode 100644 index 00000000000..505765bde27 --- /dev/null +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -0,0 +1,42 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class ModernHorizons3 extends ExpansionSet { + + private static final ModernHorizons3 instance = new ModernHorizons3(); + + public static ModernHorizons3 getInstance() { + return instance; + } + + private ModernHorizons3() { + super("Modern Horizons 3", "MH3", ExpansionSet.buildDate(2024, 6, 7), SetType.SUPPLEMENTAL_MODERN_LEGAL); + this.blockName = "Modern Horizons 3"; + this.hasBasicLands = true; + this.hasBoosters = false; // temporary + + cards.add(new SetCardInfo("Bloodstained Mire", 216, Rarity.RARE, mage.cards.b.BloodstainedMire.class)); + cards.add(new SetCardInfo("Flare of Cultivation", 154, Rarity.RARE, mage.cards.f.FlareOfCultivation.class)); + cards.add(new SetCardInfo("Flooded Strand", 220, Rarity.RARE, mage.cards.f.FloodedStrand.class)); + cards.add(new SetCardInfo("Forest", 308, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 305, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("It That Heralds the End", 9, Rarity.UNCOMMON, mage.cards.i.ItThatHeraldsTheEnd.class)); + cards.add(new SetCardInfo("Laelia, the Blade Reforged", 281, Rarity.RARE, mage.cards.l.LaeliaTheBladeReforged.class)); + cards.add(new SetCardInfo("Mountain", 307, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 304, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Polluted Delta", 224, Rarity.RARE, mage.cards.p.PollutedDelta.class)); + cards.add(new SetCardInfo("Priest of Titania", 286, Rarity.UNCOMMON, mage.cards.p.PriestOfTitania.class)); + cards.add(new SetCardInfo("Psychic Frog", 199, Rarity.RARE, mage.cards.p.PsychicFrog.class)); + cards.add(new SetCardInfo("Scurry of Gremlins", 203, Rarity.UNCOMMON, mage.cards.s.ScurryOfGremlins.class)); + cards.add(new SetCardInfo("Snow-Covered Wastes", 309, Rarity.UNCOMMON, mage.cards.s.SnowCoveredWastes.class)); + cards.add(new SetCardInfo("Swamp", 306, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Windswept Heath", 235, Rarity.RARE, mage.cards.w.WindsweptHeath.class)); + cards.add(new SetCardInfo("Wooded Foothills", 236, Rarity.RARE, mage.cards.w.WoodedFoothills.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java index 63444f323ae..cb08d1c1e74 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java @@ -4,16 +4,11 @@ import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; -import java.util.Arrays; -import java.util.List; - /** * @author TheElk801 */ public final class MurdersAtKarlovManor extends ExpansionSet { - private static final List unfinished = Arrays.asList("Alley Assailant", "Aurelia's Vindicator", "Basilica Stalker", "Bolrac-Clan Basher", "Branch of Vitu-Ghazi", "Bubble Smuggler", "Concealed Weapon", "Coveted Falcon", "Crowd-Control Warden", "Culvert Ambusher", "Defenestrated Phantom", "Dog Walker", "Essence of Antiquity", "Exit Specialist", "Expose the Culprit", "Faerie Snoop", "Flourishing Bloom-Kin", "Forum Familiar", "Fugitive Codebreaker", "Gadget Technician", "Granite Witness", "Greenbelt Radical", "Hunted Bonebrute", "Lumbering Laundry", "Mistway Spy", "Museum Nightwatch", "Nervous Gardener", "Nightdrinker Moroii", "Offender at Large", "Pyrotechnic Performer", "Rakish Scoundrel", "Riftburst Hellion", "Sanguine Savior", "Shady Informant", "Undercover Crocodelf", "Unyielding Gatekeeper", "Vengeful Creeper"); - private static final MurdersAtKarlovManor instance = new MurdersAtKarlovManor(); public static MurdersAtKarlovManor getInstance() { @@ -29,47 +24,67 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("A Killer Among Us", 167, Rarity.UNCOMMON, mage.cards.a.AKillerAmongUs.class)); cards.add(new SetCardInfo("Absolving Lammasu", 2, Rarity.UNCOMMON, mage.cards.a.AbsolvingLammasu.class)); cards.add(new SetCardInfo("Aftermath Analyst", 148, Rarity.UNCOMMON, mage.cards.a.AftermathAnalyst.class)); + cards.add(new SetCardInfo("Agency Coroner", 75, Rarity.COMMON, mage.cards.a.AgencyCoroner.class)); cards.add(new SetCardInfo("Agrus Kos, Spirit of Justice", 184, Rarity.MYTHIC, mage.cards.a.AgrusKosSpiritOfJustice.class)); cards.add(new SetCardInfo("Airtight Alibi", 149, Rarity.COMMON, mage.cards.a.AirtightAlibi.class)); cards.add(new SetCardInfo("Alley Assailant", 76, Rarity.COMMON, mage.cards.a.AlleyAssailant.class)); cards.add(new SetCardInfo("Alquist Proft, Master Sleuth", 185, Rarity.MYTHIC, mage.cards.a.AlquistProftMasterSleuth.class)); cards.add(new SetCardInfo("Analyze the Pollen", 150, Rarity.RARE, mage.cards.a.AnalyzeThePollen.class)); cards.add(new SetCardInfo("Anzrag, the Quake-Mole", 186, Rarity.MYTHIC, mage.cards.a.AnzragTheQuakeMole.class)); + cards.add(new SetCardInfo("Archdruid's Charm", 151, Rarity.RARE, mage.cards.a.ArchdruidsCharm.class)); cards.add(new SetCardInfo("Assassin's Trophy", 187, Rarity.RARE, mage.cards.a.AssassinsTrophy.class)); + cards.add(new SetCardInfo("Assemble the Players", 3, Rarity.RARE, mage.cards.a.AssembleThePlayers.class)); cards.add(new SetCardInfo("Audience with Trostani", 152, Rarity.RARE, mage.cards.a.AudienceWithTrostani.class)); cards.add(new SetCardInfo("Aurelia, the Law Above", 188, Rarity.RARE, mage.cards.a.AureliaTheLawAbove.class)); cards.add(new SetCardInfo("Auspicious Arrival", 5, Rarity.COMMON, mage.cards.a.AuspiciousArrival.class)); cards.add(new SetCardInfo("Axebane Ferox", 153, Rarity.RARE, mage.cards.a.AxebaneFerox.class)); cards.add(new SetCardInfo("Barbed Servitor", 77, Rarity.RARE, mage.cards.b.BarbedServitor.class)); cards.add(new SetCardInfo("Basilica Stalker", 78, Rarity.COMMON, mage.cards.b.BasilicaStalker.class)); + cards.add(new SetCardInfo("Behind the Mask", 39, Rarity.COMMON, mage.cards.b.BehindTheMask.class)); cards.add(new SetCardInfo("Benthic Criminologists", 40, Rarity.COMMON, mage.cards.b.BenthicCriminologists.class)); + cards.add(new SetCardInfo("Blood Spatter Analysis", 189, Rarity.RARE, mage.cards.b.BloodSpatterAnalysis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood Spatter Analysis", 413, Rarity.RARE, mage.cards.b.BloodSpatterAnalysis.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bolrac-Clan Basher", 112, Rarity.UNCOMMON, mage.cards.b.BolracClanBasher.class)); cards.add(new SetCardInfo("Branch of Vitu-Ghazi", 258, Rarity.UNCOMMON, mage.cards.b.BranchOfVituGhazi.class)); cards.add(new SetCardInfo("Burden of Proof", 42, Rarity.UNCOMMON, mage.cards.b.BurdenOfProof.class)); + cards.add(new SetCardInfo("Buried in the Garden", 191, Rarity.UNCOMMON, mage.cards.b.BuriedInTheGarden.class)); cards.add(new SetCardInfo("Candlestick", 43, Rarity.UNCOMMON, mage.cards.c.Candlestick.class)); cards.add(new SetCardInfo("Case of the Burning Masks", 113, Rarity.UNCOMMON, mage.cards.c.CaseOfTheBurningMasks.class)); cards.add(new SetCardInfo("Case of the Crimson Pulse", 114, Rarity.RARE, mage.cards.c.CaseOfTheCrimsonPulse.class)); cards.add(new SetCardInfo("Case of the Filched Falcon", 44, Rarity.UNCOMMON, mage.cards.c.CaseOfTheFilchedFalcon.class)); + cards.add(new SetCardInfo("Case of the Gateway Express", 8, Rarity.UNCOMMON, mage.cards.c.CaseOfTheGatewayExpress.class)); + cards.add(new SetCardInfo("Case of the Gorgon's Kiss", 79, Rarity.UNCOMMON, mage.cards.c.CaseOfTheGorgonsKiss.class)); cards.add(new SetCardInfo("Case of the Locked Hothouse", 155, Rarity.RARE, mage.cards.c.CaseOfTheLockedHothouse.class)); cards.add(new SetCardInfo("Case of the Pilfered Proof", 9, Rarity.UNCOMMON, mage.cards.c.CaseOfThePilferedProof.class)); + cards.add(new SetCardInfo("Case of the Ransacked Lab", 45, Rarity.RARE, mage.cards.c.CaseOfTheRansackedLab.class)); + cards.add(new SetCardInfo("Case of the Shattered Pact", 1, Rarity.UNCOMMON, mage.cards.c.CaseOfTheShatteredPact.class)); + cards.add(new SetCardInfo("Case of the Stashed Skeleton", 80, Rarity.RARE, mage.cards.c.CaseOfTheStashedSkeleton.class)); + cards.add(new SetCardInfo("Case of the Trampled Garden", 156, Rarity.UNCOMMON, mage.cards.c.CaseOfTheTrampledGarden.class)); + cards.add(new SetCardInfo("Case of the Uneaten Feast", 10, Rarity.RARE, mage.cards.c.CaseOfTheUneatenFeast.class)); cards.add(new SetCardInfo("Caught Red-Handed", 115, Rarity.UNCOMMON, mage.cards.c.CaughtRedHanded.class)); cards.add(new SetCardInfo("Cease // Desist", 246, Rarity.UNCOMMON, mage.cards.c.CeaseDesist.class)); cards.add(new SetCardInfo("Cerebral Confiscation", 81, Rarity.COMMON, mage.cards.c.CerebralConfiscation.class)); cards.add(new SetCardInfo("Chalk Outline", 157, Rarity.UNCOMMON, mage.cards.c.ChalkOutline.class)); cards.add(new SetCardInfo("Clandestine Meddler", 82, Rarity.UNCOMMON, mage.cards.c.ClandestineMeddler.class)); + cards.add(new SetCardInfo("Coerced to Kill", 192, Rarity.UNCOMMON, mage.cards.c.CoercedToKill.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Coerced to Kill", 311, Rarity.UNCOMMON, mage.cards.c.CoercedToKill.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cold Case Cracker", 46, Rarity.COMMON, mage.cards.c.ColdCaseCracker.class)); cards.add(new SetCardInfo("Commercial District", 259, Rarity.RARE, mage.cards.c.CommercialDistrict.class)); cards.add(new SetCardInfo("Concealed Weapon", 117, Rarity.UNCOMMON, mage.cards.c.ConcealedWeapon.class)); + cards.add(new SetCardInfo("Connecting the Dots", 118, Rarity.RARE, mage.cards.c.ConnectingTheDots.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Connecting the Dots", 403, Rarity.RARE, mage.cards.c.ConnectingTheDots.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class)); cards.add(new SetCardInfo("Cornered Crook", 120, Rarity.UNCOMMON, mage.cards.c.CorneredCrook.class)); cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class)); cards.add(new SetCardInfo("Crimestopper Sprite", 49, Rarity.COMMON, mage.cards.c.CrimestopperSprite.class)); + cards.add(new SetCardInfo("Cryptex", 251, Rarity.RARE, mage.cards.c.Cryptex.class)); cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class)); cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class)); cards.add(new SetCardInfo("Curious Inquiry", 51, Rarity.UNCOMMON, mage.cards.c.CuriousInquiry.class)); cards.add(new SetCardInfo("Deadly Complication", 195, Rarity.UNCOMMON, mage.cards.d.DeadlyComplication.class)); cards.add(new SetCardInfo("Deduce", 52, Rarity.COMMON, mage.cards.d.Deduce.class)); cards.add(new SetCardInfo("Defenestrated Phantom", 11, Rarity.COMMON, mage.cards.d.DefenestratedPhantom.class)); + cards.add(new SetCardInfo("Delney, Streetwise Lookout", 12, Rarity.MYTHIC, mage.cards.d.DelneyStreetwiseLookout.class)); cards.add(new SetCardInfo("Demand Answers", 122, Rarity.COMMON, mage.cards.d.DemandAnswers.class)); cards.add(new SetCardInfo("Detective's Satchel", 196, Rarity.UNCOMMON, mage.cards.d.DetectivesSatchel.class)); cards.add(new SetCardInfo("Dog Walker", 197, Rarity.COMMON, mage.cards.d.DogWalker.class)); @@ -84,12 +99,17 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Essence of Antiquity", 15, Rarity.UNCOMMON, mage.cards.e.EssenceOfAntiquity.class)); cards.add(new SetCardInfo("Evidence Examiner", 201, Rarity.UNCOMMON, mage.cards.e.EvidenceExaminer.class)); cards.add(new SetCardInfo("Exit Specialist", 55, Rarity.UNCOMMON, mage.cards.e.ExitSpecialist.class)); + cards.add(new SetCardInfo("Expedited Inheritance", 123, Rarity.MYTHIC, mage.cards.e.ExpeditedInheritance.class)); cards.add(new SetCardInfo("Ezrim, Agency Chief", 202, Rarity.RARE, mage.cards.e.EzrimAgencyChief.class)); cards.add(new SetCardInfo("Fae Flight", 56, Rarity.UNCOMMON, mage.cards.f.FaeFlight.class)); cards.add(new SetCardInfo("Faerie Snoop", 203, Rarity.COMMON, mage.cards.f.FaerieSnoop.class)); cards.add(new SetCardInfo("Fanatical Strength", 159, Rarity.COMMON, mage.cards.f.FanaticalStrength.class)); cards.add(new SetCardInfo("Felonious Rage", 125, Rarity.COMMON, mage.cards.f.FeloniousRage.class)); cards.add(new SetCardInfo("Festerleech", 85, Rarity.UNCOMMON, mage.cards.f.Festerleech.class)); + cards.add(new SetCardInfo("Flotsam // Jetsam", 247, Rarity.UNCOMMON, mage.cards.f.FlotsamJetsam.class)); + cards.add(new SetCardInfo("Flourishing Bloom-Kin", 160, Rarity.UNCOMMON, mage.cards.f.FlourishingBloomKin.class)); + cards.add(new SetCardInfo("Forensic Gadgeteer", 57, Rarity.RARE, mage.cards.f.ForensicGadgeteer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forensic Gadgeteer", 342, Rarity.RARE, mage.cards.f.ForensicGadgeteer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forensic Researcher", 58, Rarity.UNCOMMON, mage.cards.f.ForensicResearcher.class)); cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forum Familiar", 16, Rarity.UNCOMMON, mage.cards.f.ForumFamiliar.class)); @@ -101,6 +121,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Get a Leg Up", 161, Rarity.UNCOMMON, mage.cards.g.GetALegUp.class)); cards.add(new SetCardInfo("Gleaming Geardrake", 205, Rarity.UNCOMMON, mage.cards.g.GleamingGeardrake.class)); cards.add(new SetCardInfo("Glint Weaver", 162, Rarity.UNCOMMON, mage.cards.g.GlintWeaver.class)); + cards.add(new SetCardInfo("Goblin Maskmaker", 130, Rarity.COMMON, mage.cards.g.GoblinMaskmaker.class)); cards.add(new SetCardInfo("Granite Witness", 206, Rarity.COMMON, mage.cards.g.GraniteWitness.class)); cards.add(new SetCardInfo("Gravestone Strider", 252, Rarity.COMMON, mage.cards.g.GravestoneStrider.class)); cards.add(new SetCardInfo("Greenbelt Radical", 163, Rarity.UNCOMMON, mage.cards.g.GreenbeltRadical.class)); @@ -109,21 +130,35 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Hard-Hitting Question", 164, Rarity.UNCOMMON, mage.cards.h.HardHittingQuestion.class)); cards.add(new SetCardInfo("Harried Dronesmith", 131, Rarity.UNCOMMON, mage.cards.h.HarriedDronesmith.class)); cards.add(new SetCardInfo("Hedge Maze", 262, Rarity.RARE, mage.cards.h.HedgeMaze.class)); + cards.add(new SetCardInfo("Hedge Whisperer", 165, Rarity.UNCOMMON, mage.cards.h.HedgeWhisperer.class)); cards.add(new SetCardInfo("Homicide Investigator", 86, Rarity.RARE, mage.cards.h.HomicideInvestigator.class)); cards.add(new SetCardInfo("Hotshot Investigators", 60, Rarity.COMMON, mage.cards.h.HotshotInvestigators.class)); cards.add(new SetCardInfo("Hunted Bonebrute", 87, Rarity.RARE, mage.cards.h.HuntedBonebrute.class)); cards.add(new SetCardInfo("Hustle // Bustle", 249, Rarity.UNCOMMON, mage.cards.h.HustleBustle.class)); cards.add(new SetCardInfo("Ill-Timed Explosion", 207, Rarity.RARE, mage.cards.i.IllTimedExplosion.class)); + cards.add(new SetCardInfo("Illicit Masquerade", 88, Rarity.RARE, mage.cards.i.IllicitMasquerade.class)); + cards.add(new SetCardInfo("Incinerator of the Guilty", 132, Rarity.MYTHIC, mage.cards.i.IncineratorOfTheGuilty.class)); + cards.add(new SetCardInfo("Innocent Bystander", 133, Rarity.COMMON, mage.cards.i.InnocentBystander.class)); cards.add(new SetCardInfo("Inside Source", 19, Rarity.COMMON, mage.cards.i.InsideSource.class)); cards.add(new SetCardInfo("Insidious Roots", 208, Rarity.UNCOMMON, mage.cards.i.InsidiousRoots.class)); + cards.add(new SetCardInfo("Intrude on the Mind", 61, Rarity.MYTHIC, mage.cards.i.IntrudeOnTheMind.class)); cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("It Doesn't Add Up", 89, Rarity.UNCOMMON, mage.cards.i.ItDoesntAddUp.class)); cards.add(new SetCardInfo("Izoni, Center of the Web", 209, Rarity.RARE, mage.cards.i.IzoniCenterOfTheWeb.class)); cards.add(new SetCardInfo("Jaded Analyst", 62, Rarity.COMMON, mage.cards.j.JadedAnalyst.class)); + cards.add(new SetCardInfo("Judith, Carnage Connoisseur", 210, Rarity.RARE, mage.cards.j.JudithCarnageConnoisseur.class)); + cards.add(new SetCardInfo("Karlov Watchdog", 20, Rarity.UNCOMMON, mage.cards.k.KarlovWatchdog.class)); + cards.add(new SetCardInfo("Kaya, Spirits' Justice", 211, Rarity.MYTHIC, mage.cards.k.KayaSpiritsJustice.class)); + cards.add(new SetCardInfo("Kellan, Inquisitive Prodigy", 212, Rarity.RARE, mage.cards.k.KellanInquisitiveProdigy.class)); cards.add(new SetCardInfo("Knife", 134, Rarity.UNCOMMON, mage.cards.k.Knife.class)); cards.add(new SetCardInfo("Kraul Whipcracker", 213, Rarity.UNCOMMON, mage.cards.k.KraulWhipcracker.class)); + cards.add(new SetCardInfo("Krenko's Buzzcrusher", 136, Rarity.RARE, mage.cards.k.KrenkosBuzzcrusher.class)); cards.add(new SetCardInfo("Krenko, Baron of Tin Street", 135, Rarity.RARE, mage.cards.k.KrenkoBaronOfTinStreet.class)); cards.add(new SetCardInfo("Krovod Haunch", 21, Rarity.UNCOMMON, mage.cards.k.KrovodHaunch.class)); + cards.add(new SetCardInfo("Kylox's Voltstrider", 215, Rarity.MYTHIC, mage.cards.k.KyloxsVoltstrider.class)); cards.add(new SetCardInfo("Kylox, Visionary Inventor", 214, Rarity.RARE, mage.cards.k.KyloxVisionaryInventor.class)); + cards.add(new SetCardInfo("Lamplight Phoenix", 137, Rarity.RARE, mage.cards.l.LamplightPhoenix.class)); + cards.add(new SetCardInfo("Lazav, Wearer of Faces", 216, Rarity.RARE, mage.cards.l.LazavWearerOfFaces.class)); cards.add(new SetCardInfo("Lead Pipe", 90, Rarity.UNCOMMON, mage.cards.l.LeadPipe.class)); cards.add(new SetCardInfo("Leering Onlooker", 91, Rarity.UNCOMMON, mage.cards.l.LeeringOnlooker.class)); cards.add(new SetCardInfo("Leyline of the Guildpact", 217, Rarity.RARE, mage.cards.l.LeylineOfTheGuildpact.class)); @@ -134,12 +169,14 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Loxodon Eavesdropper", 168, Rarity.COMMON, mage.cards.l.LoxodonEavesdropper.class)); cards.add(new SetCardInfo("Lush Portico", 263, Rarity.RARE, mage.cards.l.LushPortico.class)); cards.add(new SetCardInfo("Macabre Reconstruction", 93, Rarity.COMMON, mage.cards.m.MacabreReconstruction.class)); + cards.add(new SetCardInfo("Magnetic Snuffler", 254, Rarity.UNCOMMON, mage.cards.m.MagneticSnuffler.class)); cards.add(new SetCardInfo("Magnifying Glass", 255, Rarity.COMMON, mage.cards.m.MagnifyingGlass.class)); cards.add(new SetCardInfo("Make Your Move", 22, Rarity.COMMON, mage.cards.m.MakeYourMove.class)); cards.add(new SetCardInfo("Makeshift Binding", 23, Rarity.COMMON, mage.cards.m.MakeshiftBinding.class)); cards.add(new SetCardInfo("Marketwatch Phantom", 24, Rarity.COMMON, mage.cards.m.MarketwatchPhantom.class)); cards.add(new SetCardInfo("Massacre Girl, Known Killer", 94, Rarity.MYTHIC, mage.cards.m.MassacreGirlKnownKiller.class)); cards.add(new SetCardInfo("Meddling Youths", 219, Rarity.UNCOMMON, mage.cards.m.MeddlingYouths.class)); + cards.add(new SetCardInfo("Melek, Reforged Researcher", 430, Rarity.MYTHIC, mage.cards.m.MelekReforgedResearcher.class)); cards.add(new SetCardInfo("Meticulous Archive", 264, Rarity.RARE, mage.cards.m.MeticulousArchive.class)); cards.add(new SetCardInfo("Mistway Spy", 65, Rarity.UNCOMMON, mage.cards.m.MistwaySpy.class)); cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); @@ -148,26 +185,39 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Neighborhood Guardian", 26, Rarity.UNCOMMON, mage.cards.n.NeighborhoodGuardian.class)); cards.add(new SetCardInfo("Nervous Gardener", 169, Rarity.COMMON, mage.cards.n.NervousGardener.class)); cards.add(new SetCardInfo("Nightdrinker Moroii", 96, Rarity.UNCOMMON, mage.cards.n.NightdrinkerMoroii.class)); + cards.add(new SetCardInfo("Niv-Mizzet, Guildpact", 220, Rarity.RARE, mage.cards.n.NivMizzetGuildpact.class)); cards.add(new SetCardInfo("No More Lies", 221, Rarity.UNCOMMON, mage.cards.n.NoMoreLies.class)); cards.add(new SetCardInfo("No Witnesses", 27, Rarity.RARE, mage.cards.n.NoWitnesses.class)); cards.add(new SetCardInfo("Not on My Watch", 28, Rarity.UNCOMMON, mage.cards.n.NotOnMyWatch.class)); cards.add(new SetCardInfo("Novice Inspector", 29, Rarity.COMMON, mage.cards.n.NoviceInspector.class)); cards.add(new SetCardInfo("Offender at Large", 138, Rarity.COMMON, mage.cards.o.OffenderAtLarge.class)); + cards.add(new SetCardInfo("Officious Interrogation", 222, Rarity.RARE, mage.cards.o.OfficiousInterrogation.class)); cards.add(new SetCardInfo("On the Job", 30, Rarity.COMMON, mage.cards.o.OnTheJob.class)); cards.add(new SetCardInfo("Out Cold", 66, Rarity.COMMON, mage.cards.o.OutCold.class)); + cards.add(new SetCardInfo("Outrageous Robbery", 97, Rarity.RARE, mage.cards.o.OutrageousRobbery.class)); cards.add(new SetCardInfo("Person of Interest", 139, Rarity.COMMON, mage.cards.p.PersonOfInterest.class)); cards.add(new SetCardInfo("Persuasive Interrogators", 98, Rarity.UNCOMMON, mage.cards.p.PersuasiveInterrogators.class)); cards.add(new SetCardInfo("Pick Your Poison", 170, Rarity.COMMON, mage.cards.p.PickYourPoison.class)); cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Polygraph Orb", 99, Rarity.UNCOMMON, mage.cards.p.PolygraphOrb.class)); cards.add(new SetCardInfo("Pompous Gadabout", 171, Rarity.UNCOMMON, mage.cards.p.PompousGadabout.class)); + cards.add(new SetCardInfo("Presumed Dead", 100, Rarity.UNCOMMON, mage.cards.p.PresumedDead.class)); cards.add(new SetCardInfo("Private Eye", 223, Rarity.UNCOMMON, mage.cards.p.PrivateEye.class)); + cards.add(new SetCardInfo("Proft's Eidetic Memory", 67, Rarity.RARE, mage.cards.p.ProftsEideticMemory.class)); cards.add(new SetCardInfo("Public Thoroughfare", 265, Rarity.COMMON, mage.cards.p.PublicThoroughfare.class)); + cards.add(new SetCardInfo("Push // Pull", 250, Rarity.UNCOMMON, mage.cards.p.PushPull.class)); + cards.add(new SetCardInfo("Pyrotechnic Performer", 140, Rarity.RARE, mage.cards.p.PyrotechnicPerformer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pyrotechnic Performer", 407, Rarity.RARE, mage.cards.p.PyrotechnicPerformer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos, Patron of Chaos", 224, Rarity.MYTHIC, mage.cards.r.RakdosPatronOfChaos.class)); cards.add(new SetCardInfo("Rakish Scoundrel", 225, Rarity.COMMON, mage.cards.r.RakishScoundrel.class)); cards.add(new SetCardInfo("Raucous Theater", 266, Rarity.RARE, mage.cards.r.RaucousTheater.class)); cards.add(new SetCardInfo("Reasonable Doubt", 69, Rarity.COMMON, mage.cards.r.ReasonableDoubt.class)); cards.add(new SetCardInfo("Reckless Detective", 141, Rarity.UNCOMMON, mage.cards.r.RecklessDetective.class)); cards.add(new SetCardInfo("Red Herring", 142, Rarity.COMMON, mage.cards.r.RedHerring.class)); + cards.add(new SetCardInfo("Reenact the Crime", 70, Rarity.RARE, mage.cards.r.ReenactTheCrime.class)); + cards.add(new SetCardInfo("Relive the Past", 226, Rarity.RARE, mage.cards.r.ReliveThePast.class)); cards.add(new SetCardInfo("Repeat Offender", 101, Rarity.COMMON, mage.cards.r.RepeatOffender.class)); + cards.add(new SetCardInfo("Repulsive Mutation", 227, Rarity.UNCOMMON, mage.cards.r.RepulsiveMutation.class)); cards.add(new SetCardInfo("Riftburst Hellion", 228, Rarity.COMMON, mage.cards.r.RiftburstHellion.class)); cards.add(new SetCardInfo("Rope", 173, Rarity.UNCOMMON, mage.cards.r.Rope.class)); cards.add(new SetCardInfo("Rot Farm Mortipede", 102, Rarity.COMMON, mage.cards.r.RotFarmMortipede.class)); @@ -182,6 +232,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Seasoned Consultant", 33, Rarity.COMMON, mage.cards.s.SeasonedConsultant.class)); cards.add(new SetCardInfo("Shadowy Backstreet", 268, Rarity.RARE, mage.cards.s.ShadowyBackstreet.class)); cards.add(new SetCardInfo("Shady Informant", 231, Rarity.COMMON, mage.cards.s.ShadyInformant.class)); + cards.add(new SetCardInfo("Sharp-Eyed Rookie", 176, Rarity.RARE, mage.cards.s.SharpEyedRookie.class)); cards.add(new SetCardInfo("Shock", 144, Rarity.COMMON, mage.cards.s.Shock.class)); cards.add(new SetCardInfo("Slice from the Shadows", 103, Rarity.COMMON, mage.cards.s.SliceFromTheShadows.class)); cards.add(new SetCardInfo("Slime Against Humanity", 177, Rarity.COMMON, mage.cards.s.SlimeAgainstHumanity.class)); @@ -195,22 +246,29 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Surveillance Monitor", 73, Rarity.UNCOMMON, mage.cards.s.SurveillanceMonitor.class)); cards.add(new SetCardInfo("Suspicious Detonation", 145, Rarity.COMMON, mage.cards.s.SuspiciousDetonation.class)); cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Tenth District Hero", 34, Rarity.RARE, mage.cards.t.TenthDistrictHero.class)); cards.add(new SetCardInfo("Teysa, Opulent Oligarch", 234, Rarity.RARE, mage.cards.t.TeysaOpulentOligarch.class)); cards.add(new SetCardInfo("The Chase Is On", 116, Rarity.COMMON, mage.cards.t.TheChaseIsOn.class)); + cards.add(new SetCardInfo("The Pride of Hull Clade", 172, Rarity.MYTHIC, mage.cards.t.ThePrideOfHullClade.class)); cards.add(new SetCardInfo("They Went This Way", 178, Rarity.COMMON, mage.cards.t.TheyWentThisWay.class)); cards.add(new SetCardInfo("Thinking Cap", 257, Rarity.COMMON, mage.cards.t.ThinkingCap.class)); cards.add(new SetCardInfo("Thundering Falls", 269, Rarity.RARE, mage.cards.t.ThunderingFalls.class)); + cards.add(new SetCardInfo("Tin Street Gossip", 235, Rarity.UNCOMMON, mage.cards.t.TinStreetGossip.class)); + cards.add(new SetCardInfo("Tolsimir, Midnight's Light", 236, Rarity.RARE, mage.cards.t.TolsimirMidnightsLight.class)); + cards.add(new SetCardInfo("Tomik, Wielder of Law", 431, Rarity.MYTHIC, mage.cards.t.TomikWielderOfLaw.class)); cards.add(new SetCardInfo("Topiary Panther", 179, Rarity.COMMON, mage.cards.t.TopiaryPanther.class)); cards.add(new SetCardInfo("Torch the Witness", 146, Rarity.UNCOMMON, mage.cards.t.TorchTheWitness.class)); cards.add(new SetCardInfo("Toxin Analysis", 107, Rarity.COMMON, mage.cards.t.ToxinAnalysis.class)); cards.add(new SetCardInfo("Treacherous Greed", 237, Rarity.RARE, mage.cards.t.TreacherousGreed.class)); cards.add(new SetCardInfo("Trostani, Three Whispers", 238, Rarity.MYTHIC, mage.cards.t.TrostaniThreeWhispers.class)); + cards.add(new SetCardInfo("Tunnel Tipster", 180, Rarity.COMMON, mage.cards.t.TunnelTipster.class)); cards.add(new SetCardInfo("Unauthorized Exit", 74, Rarity.COMMON, mage.cards.u.UnauthorizedExit.class)); cards.add(new SetCardInfo("Undercity Eliminator", 108, Rarity.UNCOMMON, mage.cards.u.UndercityEliminator.class)); cards.add(new SetCardInfo("Undercity Sewers", 270, Rarity.RARE, mage.cards.u.UndercitySewers.class)); cards.add(new SetCardInfo("Undercover Crocodelf", 239, Rarity.COMMON, mage.cards.u.UndercoverCrocodelf.class)); cards.add(new SetCardInfo("Underground Mortuary", 271, Rarity.RARE, mage.cards.u.UndergroundMortuary.class)); cards.add(new SetCardInfo("Undergrowth Recon", 181, Rarity.MYTHIC, mage.cards.u.UndergrowthRecon.class)); + cards.add(new SetCardInfo("Unscrupulous Agent", 109, Rarity.COMMON, mage.cards.u.UnscrupulousAgent.class)); cards.add(new SetCardInfo("Urgent Necropsy", 240, Rarity.MYTHIC, mage.cards.u.UrgentNecropsy.class)); cards.add(new SetCardInfo("Vein Ripper", 110, Rarity.MYTHIC, mage.cards.v.VeinRipper.class)); cards.add(new SetCardInfo("Vengeful Creeper", 182, Rarity.COMMON, mage.cards.v.VengefulCreeper.class)); @@ -220,8 +278,8 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Warleader's Call", 242, Rarity.RARE, mage.cards.w.WarleadersCall.class)); cards.add(new SetCardInfo("Wispdrinker Vampire", 243, Rarity.UNCOMMON, mage.cards.w.WispdrinkerVampire.class)); cards.add(new SetCardInfo("Wojek Investigator", 36, Rarity.RARE, mage.cards.w.WojekInvestigator.class)); + cards.add(new SetCardInfo("Worldsoul's Rage", 244, Rarity.RARE, mage.cards.w.WorldsoulsRage.class)); cards.add(new SetCardInfo("Wrench", 37, Rarity.UNCOMMON, mage.cards.w.Wrench.class)); - - cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented + cards.add(new SetCardInfo("Yarus, Roar of the Old Gods", 245, Rarity.RARE, mage.cards.y.YarusRoarOfTheOldGods.class)); } } diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index 61dfa5a3b53..4ba1405f3ea 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -33,10 +33,12 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Animate Dead", 125, Rarity.UNCOMMON, mage.cards.a.AnimateDead.class)); cards.add(new SetCardInfo("Anya, Merciless Angel", 199, Rarity.MYTHIC, mage.cards.a.AnyaMercilessAngel.class)); cards.add(new SetCardInfo("Arcane Signet", 223, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Armed with Proof", 9, Rarity.RARE, mage.cards.a.ArmedWithProof.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Armed with Proof", 320, Rarity.RARE, mage.cards.a.ArmedWithProof.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ash Barrens", 248, Rarity.UNCOMMON, mage.cards.a.AshBarrens.class)); cards.add(new SetCardInfo("Ashcloud Phoenix", 147, Rarity.MYTHIC, mage.cards.a.AshcloudPhoenix.class)); cards.add(new SetCardInfo("Austere Command", 56, Rarity.RARE, mage.cards.a.AustereCommand.class)); - cards.add(new SetCardInfo("Azorius Chancery", 249, Rarity.COMMON, mage.cards.a.AzoriusChancery.class)); + cards.add(new SetCardInfo("Azorius Chancery", 249, Rarity.UNCOMMON, mage.cards.a.AzoriusChancery.class)); cards.add(new SetCardInfo("Azorius Signet", 224, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class)); cards.add(new SetCardInfo("Baleful Strix", 200, Rarity.RARE, mage.cards.b.BalefulStrix.class)); cards.add(new SetCardInfo("Beast Whisperer", 166, Rarity.RARE, mage.cards.b.BeastWhisperer.class)); @@ -50,8 +52,10 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Brash Taunter", 148, Rarity.RARE, mage.cards.b.BrashTaunter.class)); cards.add(new SetCardInfo("Broodhatch Nantuko", 167, Rarity.UNCOMMON, mage.cards.b.BroodhatchNantuko.class)); cards.add(new SetCardInfo("Canopy Vista", 252, Rarity.RARE, mage.cards.c.CanopyVista.class)); + cards.add(new SetCardInfo("Case of the Shifting Visage", 19, Rarity.RARE, mage.cards.c.CaseOfTheShiftingVisage.class)); cards.add(new SetCardInfo("Castle Ardenvale", 253, Rarity.RARE, mage.cards.c.CastleArdenvale.class)); cards.add(new SetCardInfo("Chaos Warp", 149, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Charnel Serenade", 26, Rarity.RARE, mage.cards.c.CharnelSerenade.class)); cards.add(new SetCardInfo("Choked Estuary", 254, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); cards.add(new SetCardInfo("Chulane, Teller of Tales", 202, Rarity.MYTHIC, mage.cards.c.ChulaneTellerOfTales.class)); cards.add(new SetCardInfo("Cinder Glade", 255, Rarity.RARE, mage.cards.c.CinderGlade.class)); @@ -60,6 +64,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Confirm Suspicions", 97, Rarity.RARE, mage.cards.c.ConfirmSuspicions.class)); cards.add(new SetCardInfo("Connive // Concoct", 203, Rarity.RARE, mage.cards.c.ConniveConcoct.class)); cards.add(new SetCardInfo("Consider", 98, Rarity.COMMON, mage.cards.c.Consider.class)); + cards.add(new SetCardInfo("Copy Catchers", 20, Rarity.RARE, mage.cards.c.CopyCatchers.class)); + cards.add(new SetCardInfo("Counterpoint", 41, Rarity.RARE, mage.cards.c.Counterpoint.class)); cards.add(new SetCardInfo("Curate", 99, Rarity.COMMON, mage.cards.c.Curate.class)); cards.add(new SetCardInfo("Curse of Opulence", 150, Rarity.UNCOMMON, mage.cards.c.CurseOfOpulence.class)); cards.add(new SetCardInfo("Darien, King of Kjeldor", 59, Rarity.RARE, mage.cards.d.DarienKingOfKjeldor.class)); @@ -69,6 +75,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Deep Analysis", 100, Rarity.COMMON, mage.cards.d.DeepAnalysis.class)); cards.add(new SetCardInfo("Deflecting Palm", 205, Rarity.RARE, mage.cards.d.DeflectingPalm.class)); cards.add(new SetCardInfo("Den Protector", 169, Rarity.RARE, mage.cards.d.DenProtector.class)); + cards.add(new SetCardInfo("Detective of the Month", 21, Rarity.RARE, mage.cards.d.DetectiveOfTheMonth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Detective of the Month", 331, Rarity.RARE, mage.cards.d.DetectiveOfTheMonth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dimir Aqueduct", 258, Rarity.UNCOMMON, mage.cards.d.DimirAqueduct.class)); cards.add(new SetCardInfo("Dimir Signet", 226, Rarity.UNCOMMON, mage.cards.d.DimirSignet.class)); cards.add(new SetCardInfo("Dimir Spybug", 206, Rarity.UNCOMMON, mage.cards.d.DimirSpybug.class)); @@ -81,6 +89,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Dream Eater", 101, Rarity.MYTHIC, mage.cards.d.DreamEater.class)); cards.add(new SetCardInfo("Drownyard Temple", 259, Rarity.RARE, mage.cards.d.DrownyardTemple.class)); cards.add(new SetCardInfo("Duelist's Heritage", 60, Rarity.RARE, mage.cards.d.DuelistsHeritage.class)); + cards.add(new SetCardInfo("Duskana, the Rage Mother", 5, Rarity.MYTHIC, mage.cards.d.DuskanaTheRageMother.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Duskana, the Rage Mother", 312, Rarity.MYTHIC, mage.cards.d.DuskanaTheRageMother.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dusk // Dawn", 61, Rarity.RARE, mage.cards.d.DuskDawn.class)); cards.add(new SetCardInfo("Elspeth, Sun's Champion", 62, Rarity.MYTHIC, mage.cards.e.ElspethSunsChampion.class)); cards.add(new SetCardInfo("Enhanced Surveillance", 102, Rarity.UNCOMMON, mage.cards.e.EnhancedSurveillance.class)); @@ -92,20 +102,26 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Everflowing Chalice", 227, Rarity.UNCOMMON, mage.cards.e.EverflowingChalice.class)); cards.add(new SetCardInfo("Exalted Angel", 63, Rarity.RARE, mage.cards.e.ExaltedAngel.class)); cards.add(new SetCardInfo("Exotic Orchard", 260, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Experiment Twelve", 37, Rarity.RARE, mage.cards.e.ExperimentTwelve.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Experiment Twelve", 347, Rarity.RARE, mage.cards.e.ExperimentTwelve.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Farewell", 64, Rarity.RARE, mage.cards.f.Farewell.class)); cards.add(new SetCardInfo("Fell the Mighty", 65, Rarity.RARE, mage.cards.f.FellTheMighty.class)); cards.add(new SetCardInfo("Fellwar Stone", 228, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Fetid Pools", 261, Rarity.RARE, mage.cards.f.FetidPools.class)); cards.add(new SetCardInfo("Fiendish Duo", 153, Rarity.MYTHIC, mage.cards.f.FiendishDuo.class)); + cards.add(new SetCardInfo("Final-Word Phantom", 22, Rarity.RARE, mage.cards.f.FinalWordPhantom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Final-Word Phantom", 332, Rarity.RARE, mage.cards.f.FinalWordPhantom.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Finale of Revelation", 106, Rarity.MYTHIC, mage.cards.f.FinaleOfRevelation.class)); + cards.add(new SetCardInfo("Follow the Bodies", 23, Rarity.RARE, mage.cards.f.FollowTheBodies.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Follow the Bodies", 333, Rarity.RARE, mage.cards.f.FollowTheBodies.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fortified Village", 262, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); - cards.add(new SetCardInfo("Frontier Warmonger", 154, Rarity.UNCOMMON, mage.cards.f.FrontierWarmonger.class)); + cards.add(new SetCardInfo("Frontier Warmonger", 154, Rarity.RARE, mage.cards.f.FrontierWarmonger.class)); cards.add(new SetCardInfo("Fumigate", 66, Rarity.RARE, mage.cards.f.Fumigate.class)); cards.add(new SetCardInfo("Furycalm Snarl", 263, Rarity.RARE, mage.cards.f.FurycalmSnarl.class)); cards.add(new SetCardInfo("Game Trail", 264, Rarity.RARE, mage.cards.g.GameTrail.class)); cards.add(new SetCardInfo("Ghostly Prison", 67, Rarity.UNCOMMON, mage.cards.g.GhostlyPrison.class)); cards.add(new SetCardInfo("Gideon's Sacrifice", 68, Rarity.COMMON, mage.cards.g.GideonsSacrifice.class)); - cards.add(new SetCardInfo("Gisela, Blade of Goldnight", 211, Rarity.RARE, mage.cards.g.GiselaBladeOfGoldnight.class)); + cards.add(new SetCardInfo("Gisela, Blade of Goldnight", 211, Rarity.MYTHIC, mage.cards.g.GiselaBladeOfGoldnight.class)); cards.add(new SetCardInfo("Graf Mole", 170, Rarity.UNCOMMON, mage.cards.g.GrafMole.class)); cards.add(new SetCardInfo("Grave Titan", 129, Rarity.MYTHIC, mage.cards.g.GraveTitan.class)); cards.add(new SetCardInfo("Gruul Turf", 265, Rarity.UNCOMMON, mage.cards.g.GruulTurf.class)); @@ -113,7 +129,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Hooded Hydra", 171, Rarity.MYTHIC, mage.cards.h.HoodedHydra.class)); cards.add(new SetCardInfo("Hornet Queen", 172, Rarity.RARE, mage.cards.h.HornetQueen.class)); cards.add(new SetCardInfo("Hostile Desert", 266, Rarity.RARE, mage.cards.h.HostileDesert.class)); - cards.add(new SetCardInfo("Hydroid Krasis", 212, Rarity.MYTHIC, mage.cards.h.HydroidKrasis.class)); + cards.add(new SetCardInfo("Hydroid Krasis", 212, Rarity.RARE, mage.cards.h.HydroidKrasis.class)); cards.add(new SetCardInfo("Idol of Oblivion", 229, Rarity.RARE, mage.cards.i.IdolOfOblivion.class)); cards.add(new SetCardInfo("Imperial Hellkite", 155, Rarity.RARE, mage.cards.i.ImperialHellkite.class)); cards.add(new SetCardInfo("Inspiring Statuary", 230, Rarity.RARE, mage.cards.i.InspiringStatuary.class)); @@ -123,19 +139,22 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Jungle Shrine", 268, Rarity.UNCOMMON, mage.cards.j.JungleShrine.class)); cards.add(new SetCardInfo("Junk Winder", 107, Rarity.UNCOMMON, mage.cards.j.JunkWinder.class)); cards.add(new SetCardInfo("Kappa Cannoneer", 108, Rarity.RARE, mage.cards.k.KappaCannoneer.class)); - cards.add(new SetCardInfo("Kazuul, Tyrant of the Cliffs", 157, Rarity.UNCOMMON, mage.cards.k.KazuulTyrantOfTheCliffs.class)); + cards.add(new SetCardInfo("Kaust, Eyes of the Glade", 1, Rarity.MYTHIC, mage.cards.k.KaustEyesOfTheGlade.class)); + cards.add(new SetCardInfo("Kazuul, Tyrant of the Cliffs", 157, Rarity.RARE, mage.cards.k.KazuulTyrantOfTheCliffs.class)); cards.add(new SetCardInfo("Keeper of the Accord", 70, Rarity.RARE, mage.cards.k.KeeperOfTheAccord.class)); cards.add(new SetCardInfo("Kessig Wolf Run", 269, Rarity.RARE, mage.cards.k.KessigWolfRun.class)); cards.add(new SetCardInfo("Kher Keep", 270, Rarity.RARE, mage.cards.k.KherKeep.class)); cards.add(new SetCardInfo("Killer Service", 174, Rarity.RARE, mage.cards.k.KillerService.class)); + cards.add(new SetCardInfo("Knowledge Is Power", 42, Rarity.RARE, mage.cards.k.KnowledgeIsPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knowledge Is Power", 352, Rarity.RARE, mage.cards.k.KnowledgeIsPower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Koma, Cosmos Serpent", 213, Rarity.MYTHIC, mage.cards.k.KomaCosmosSerpent.class)); cards.add(new SetCardInfo("Krosan Cloudscraper", 175, Rarity.RARE, mage.cards.k.KrosanCloudscraper.class)); - cards.add(new SetCardInfo("Krosan Colossus", 176, Rarity.UNCOMMON, mage.cards.k.KrosanColossus.class)); + cards.add(new SetCardInfo("Krosan Colossus", 176, Rarity.RARE, mage.cards.k.KrosanColossus.class)); cards.add(new SetCardInfo("Krosan Verge", 271, Rarity.UNCOMMON, mage.cards.k.KrosanVerge.class)); cards.add(new SetCardInfo("Labyrinth of Skophos", 272, Rarity.RARE, mage.cards.l.LabyrinthOfSkophos.class)); - cards.add(new SetCardInfo("Lazav, the Multifarious", 214, Rarity.RARE, mage.cards.l.LazavTheMultifarious.class)); + cards.add(new SetCardInfo("Lazav, the Multifarious", 214, Rarity.MYTHIC, mage.cards.l.LazavTheMultifarious.class)); cards.add(new SetCardInfo("Lifecrafter's Bestiary", 231, Rarity.RARE, mage.cards.l.LifecraftersBestiary.class)); - cards.add(new SetCardInfo("Lonely Sandbar", 273, Rarity.UNCOMMON, mage.cards.l.LonelySandbar.class)); + cards.add(new SetCardInfo("Lonely Sandbar", 273, Rarity.COMMON, mage.cards.l.LonelySandbar.class)); cards.add(new SetCardInfo("Lonis, Cryptozoologist", 215, Rarity.RARE, mage.cards.l.LonisCryptozoologist.class)); cards.add(new SetCardInfo("Loran of the Third Path", 71, Rarity.RARE, mage.cards.l.LoranOfTheThirdPath.class)); cards.add(new SetCardInfo("Martial Impetus", 72, Rarity.UNCOMMON, mage.cards.m.MartialImpetus.class)); @@ -144,7 +163,12 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Master of Pearls", 73, Rarity.RARE, mage.cards.m.MasterOfPearls.class)); cards.add(new SetCardInfo("Mastery of the Unseen", 74, Rarity.RARE, mage.cards.m.MasteryOfTheUnseen.class)); cards.add(new SetCardInfo("Mechanized Production", 109, Rarity.MYTHIC, mage.cards.m.MechanizedProduction.class)); + cards.add(new SetCardInfo("Merchant of Truth", 11, Rarity.RARE, mage.cards.m.MerchantOfTruth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merchant of Truth", 322, Rarity.RARE, mage.cards.m.MerchantOfTruth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mind Stone", 232, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); + cards.add(new SetCardInfo("Mirko, Obsessive Theorist", 2, Rarity.MYTHIC, mage.cards.m.MirkoObsessiveTheorist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirko, Obsessive Theorist", 50, Rarity.MYTHIC, mage.cards.m.MirkoObsessiveTheorist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirko, Obsessive Theorist", 316, Rarity.MYTHIC, mage.cards.m.MirkoObsessiveTheorist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mirror Entity", 75, Rarity.RARE, mage.cards.m.MirrorEntity.class)); cards.add(new SetCardInfo("Mission Briefing", 110, Rarity.RARE, mage.cards.m.MissionBriefing.class)); cards.add(new SetCardInfo("Morska, Undersea Sleuth", 3, Rarity.MYTHIC, mage.cards.m.MorskaUnderseaSleuth.class)); @@ -155,7 +179,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Mystic Sanctuary", 277, Rarity.COMMON, mage.cards.m.MysticSanctuary.class)); cards.add(new SetCardInfo("Nadir Kraken", 112, Rarity.RARE, mage.cards.n.NadirKraken.class)); cards.add(new SetCardInfo("Nantuko Vigilante", 177, Rarity.COMMON, mage.cards.n.NantukoVigilante.class)); - cards.add(new SetCardInfo("Nature's Lore", 178, Rarity.COMMON, mage.cards.n.NaturesLore.class)); + cards.add(new SetCardInfo("Nature's Lore", 178, Rarity.UNCOMMON, mage.cards.n.NaturesLore.class)); cards.add(new SetCardInfo("Necromancy", 131, Rarity.UNCOMMON, mage.cards.n.Necromancy.class)); cards.add(new SetCardInfo("Needle Spires", 278, Rarity.RARE, mage.cards.n.NeedleSpires.class)); cards.add(new SetCardInfo("Neheb, the Eternal", 158, Rarity.MYTHIC, mage.cards.n.NehebTheEternal.class)); @@ -165,6 +189,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Obscuring Aether", 179, Rarity.RARE, mage.cards.o.ObscuringAether.class)); cards.add(new SetCardInfo("Ohran Frostfang", 180, Rarity.RARE, mage.cards.o.OhranFrostfang.class)); cards.add(new SetCardInfo("Ongoing Investigation", 114, Rarity.UNCOMMON, mage.cards.o.OngoingInvestigation.class)); + cards.add(new SetCardInfo("On the Trail", 39, Rarity.RARE, mage.cards.o.OnTheTrail.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("On the Trail", 349, Rarity.RARE, mage.cards.o.OnTheTrail.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Organic Extinction", 76, Rarity.RARE, mage.cards.o.OrganicExtinction.class)); cards.add(new SetCardInfo("Orzhov Advokist", 77, Rarity.UNCOMMON, mage.cards.o.OrzhovAdvokist.class)); cards.add(new SetCardInfo("Otherworldly Gaze", 115, Rarity.COMMON, mage.cards.o.OtherworldlyGaze.class)); @@ -177,10 +203,13 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Port of Karfell", 280, Rarity.UNCOMMON, mage.cards.p.PortOfKarfell.class)); cards.add(new SetCardInfo("Prairie Stream", 281, Rarity.RARE, mage.cards.p.PrairieStream.class)); cards.add(new SetCardInfo("Price of Fame", 135, Rarity.UNCOMMON, mage.cards.p.PriceOfFame.class)); + cards.add(new SetCardInfo("Printlifter Ooze", 40, Rarity.RARE, mage.cards.p.PrintlifterOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Printlifter Ooze", 350, Rarity.RARE, mage.cards.p.PrintlifterOoze.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Promise of Loyalty", 79, Rarity.RARE, mage.cards.p.PromiseOfLoyalty.class)); cards.add(new SetCardInfo("Psychosis Crawler", 234, Rarity.RARE, mage.cards.p.PsychosisCrawler.class)); cards.add(new SetCardInfo("Ravenous Chupacabra", 136, Rarity.UNCOMMON, mage.cards.r.RavenousChupacabra.class)); cards.add(new SetCardInfo("Reanimate", 137, Rarity.RARE, mage.cards.r.Reanimate.class)); + cards.add(new SetCardInfo("Redemption Arc", 13, Rarity.RARE, mage.cards.r.RedemptionArc.class)); cards.add(new SetCardInfo("Reliquary Tower", 282, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); cards.add(new SetCardInfo("Return of the Wildspeaker", 181, Rarity.RARE, mage.cards.r.ReturnOfTheWildspeaker.class)); cards.add(new SetCardInfo("Rise of the Dark Realms", 138, Rarity.MYTHIC, mage.cards.r.RiseOfTheDarkRealms.class)); @@ -196,7 +225,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Scavenger Grounds", 287, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); cards.add(new SetCardInfo("Scourge of the Throne", 160, Rarity.RARE, mage.cards.s.ScourgeOfTheThrone.class)); cards.add(new SetCardInfo("Scroll of Fate", 235, Rarity.RARE, mage.cards.s.ScrollOfFate.class)); - cards.add(new SetCardInfo("Seal of Cleansing", 80, Rarity.UNCOMMON, mage.cards.s.SealOfCleansing.class)); + cards.add(new SetCardInfo("Seal of Cleansing", 80, Rarity.COMMON, mage.cards.s.SealOfCleansing.class)); cards.add(new SetCardInfo("Search the Premises", 81, Rarity.RARE, mage.cards.s.SearchThePremises.class)); cards.add(new SetCardInfo("Seaside Citadel", 288, Rarity.UNCOMMON, mage.cards.s.SeasideCitadel.class)); cards.add(new SetCardInfo("Secluded Steppe", 289, Rarity.COMMON, mage.cards.s.SecludedSteppe.class)); @@ -208,6 +237,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Sheltered Thicket", 291, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); cards.add(new SetCardInfo("Shimmer Dragon", 117, Rarity.RARE, mage.cards.s.ShimmerDragon.class)); cards.add(new SetCardInfo("Shiny Impetus", 161, Rarity.UNCOMMON, mage.cards.s.ShinyImpetus.class)); + cards.add(new SetCardInfo("Showstopping Surprise", 35, Rarity.RARE, mage.cards.s.ShowstoppingSurprise.class)); cards.add(new SetCardInfo("Shriekmaw", 139, Rarity.UNCOMMON, mage.cards.s.Shriekmaw.class)); cards.add(new SetCardInfo("Shrine of the Forsaken Gods", 292, Rarity.RARE, mage.cards.s.ShrineOfTheForsakenGods.class)); cards.add(new SetCardInfo("Sidar Kondo of Jamuraa", 219, Rarity.MYTHIC, mage.cards.s.SidarKondoOfJamuraa.class)); @@ -219,6 +249,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Smuggler's Share", 84, Rarity.RARE, mage.cards.s.SmugglersShare.class)); cards.add(new SetCardInfo("Sol Ring", 237, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Solemn Simulacrum", 238, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); + cards.add(new SetCardInfo("Sophia, Dogged Detective", 8, Rarity.MYTHIC, mage.cards.s.SophiaDoggedDetective.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sophia, Dogged Detective", 319, Rarity.MYTHIC, mage.cards.s.SophiaDoggedDetective.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soul Snare", 85, Rarity.UNCOMMON, mage.cards.s.SoulSnare.class)); cards.add(new SetCardInfo("Spectacular Showdown", 162, Rarity.RARE, mage.cards.s.SpectacularShowdown.class)); cards.add(new SetCardInfo("Sphinx of the Second Sun", 118, Rarity.MYTHIC, mage.cards.s.SphinxOfTheSecondSun.class)); @@ -237,6 +269,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Talisman of Dominance", 242, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); cards.add(new SetCardInfo("Talisman of Progress", 243, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class)); cards.add(new SetCardInfo("Talisman of Unity", 244, Rarity.UNCOMMON, mage.cards.t.TalismanOfUnity.class)); + cards.add(new SetCardInfo("Tangletrove Kelp", 24, Rarity.RARE, mage.cards.t.TangletroveKelp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tangletrove Kelp", 334, Rarity.RARE, mage.cards.t.TangletroveKelp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Teferi's Ageless Insight", 119, Rarity.RARE, mage.cards.t.TeferisAgelessInsight.class)); cards.add(new SetCardInfo("Temple of Abandon", 301, Rarity.RARE, mage.cards.t.TempleOfAbandon.class)); cards.add(new SetCardInfo("Temple of Enlightenment", 302, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); @@ -245,6 +279,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Temple of Triumph", 306, Rarity.RARE, mage.cards.t.TempleOfTriumph.class)); cards.add(new SetCardInfo("Temple of the False God", 305, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); cards.add(new SetCardInfo("Temur War Shaman", 187, Rarity.RARE, mage.cards.t.TemurWarShaman.class)); + cards.add(new SetCardInfo("Tesak, Judith's Hellhound", 36, Rarity.RARE, mage.cards.t.TesakJudithsHellhound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tesak, Judith's Hellhound", 346, Rarity.RARE, mage.cards.t.TesakJudithsHellhound.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tezzeret, Betrayer of Flesh", 120, Rarity.MYTHIC, mage.cards.t.TezzeretBetrayerOfFlesh.class)); cards.add(new SetCardInfo("Thelonite Hermit", 188, Rarity.RARE, mage.cards.t.TheloniteHermit.class)); cards.add(new SetCardInfo("Thought Monitor", 121, Rarity.RARE, mage.cards.t.ThoughtMonitor.class)); @@ -259,15 +295,20 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Toxic Deluge", 142, Rarity.RARE, mage.cards.t.ToxicDeluge.class)); cards.add(new SetCardInfo("Trail of Mystery", 192, Rarity.RARE, mage.cards.t.TrailOfMystery.class)); cards.add(new SetCardInfo("Tranquil Thicket", 309, Rarity.COMMON, mage.cards.t.TranquilThicket.class)); + cards.add(new SetCardInfo("Trouble in Pairs", 15, Rarity.RARE, mage.cards.t.TroubleInPairs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trouble in Pairs", 326, Rarity.RARE, mage.cards.t.TroubleInPairs.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Twilight Prophet", 143, Rarity.MYTHIC, mage.cards.t.TwilightProphet.class)); cards.add(new SetCardInfo("Ugin's Mastery", 53, Rarity.RARE, mage.cards.u.UginsMastery.class)); cards.add(new SetCardInfo("Ulvenwald Mysteries", 193, Rarity.UNCOMMON, mage.cards.u.UlvenwaldMysteries.class)); + cards.add(new SetCardInfo("Unshakable Tail", 29, Rarity.RARE, mage.cards.u.UnshakableTail.class)); cards.add(new SetCardInfo("Vengeful Ancestor", 163, Rarity.RARE, mage.cards.v.VengefulAncestor.class)); cards.add(new SetCardInfo("Vizier of Many Faces", 123, Rarity.RARE, mage.cards.v.VizierOfManyFaces.class)); cards.add(new SetCardInfo("Vow of Duty", 89, Rarity.UNCOMMON, mage.cards.v.VowOfDuty.class)); cards.add(new SetCardInfo("Vow of Lightning", 164, Rarity.UNCOMMON, mage.cards.v.VowOfLightning.class)); cards.add(new SetCardInfo("Wall of Omens", 90, Rarity.UNCOMMON, mage.cards.w.WallOfOmens.class)); cards.add(new SetCardInfo("War Room", 310, Rarity.RARE, mage.cards.w.WarRoom.class)); + cards.add(new SetCardInfo("Watcher of Hours", 25, Rarity.RARE, mage.cards.w.WatcherOfHours.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Watcher of Hours", 335, Rarity.RARE, mage.cards.w.WatcherOfHours.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wavesifter", 220, Rarity.COMMON, mage.cards.w.Wavesifter.class)); cards.add(new SetCardInfo("Welcoming Vampire", 91, Rarity.RARE, mage.cards.w.WelcomingVampire.class)); cards.add(new SetCardInfo("Whirler Rogue", 124, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class)); @@ -277,7 +318,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Wilderness Reclamation", 196, Rarity.UNCOMMON, mage.cards.w.WildernessReclamation.class)); cards.add(new SetCardInfo("Windborn Muse", 92, Rarity.RARE, mage.cards.w.WindbornMuse.class)); cards.add(new SetCardInfo("Winds of Rath", 93, Rarity.RARE, mage.cards.w.WindsOfRath.class)); - cards.add(new SetCardInfo("Yedora, Grave Gardener", 197, Rarity.UNCOMMON, mage.cards.y.YedoraGraveGardener.class)); + cards.add(new SetCardInfo("Yedora, Grave Gardener", 197, Rarity.RARE, mage.cards.y.YedoraGraveGardener.class)); cards.add(new SetCardInfo("Zoetic Cavern", 311, Rarity.UNCOMMON, mage.cards.z.ZoeticCavern.class)); } } diff --git a/Mage.Sets/src/mage/sets/MysteryBooster.java b/Mage.Sets/src/mage/sets/MysteryBooster.java index 0a3cb5b9b53..17dd6cd76bf 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster.java @@ -3516,7 +3516,7 @@ public class MysteryBooster extends ExpansionSet { for (int slot = 1; slot < 16; ++slot) { final List availableCards = this.possibleCardsPerBoosterSlot.get(slot); final int printSheetCardNumber = RandomUtil.nextInt(availableCards.size()); - final Card chosenCard = availableCards.get(printSheetCardNumber).getCard(); + final Card chosenCard = availableCards.get(printSheetCardNumber).createCard(); booster.add(chosenCard); } return booster; diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java new file mode 100644 index 00000000000..f45f1059597 --- /dev/null +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java @@ -0,0 +1,31 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class OutlawsOfThunderJunction extends ExpansionSet { + + private static final OutlawsOfThunderJunction instance = new OutlawsOfThunderJunction(); + + public static OutlawsOfThunderJunction getInstance() { + return instance; + } + + private OutlawsOfThunderJunction() { + super("Outlaws of Thunder Junction", "OTJ", ExpansionSet.buildDate(2024, 4, 19), SetType.EXPANSION); + this.blockName = "Outlaws of Thunder Junction"; // for sorting in GUI + this.hasBasicLands = true; + this.hasBoosters = false; // temporary + + cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hell to Pay", 126, Rarity.RARE, mage.cards.h.HellToPay.class)); + cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + } +} diff --git a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java index 380468ee7af..bbdc4825ff8 100644 --- a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java +++ b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java @@ -20,6 +20,7 @@ public final class RavnicaClueEdition extends ExpansionSet { this.hasBasicLands = true; this.hasBoosters = false; + cards.add(new SetCardInfo("Aegis of the Legion", 22, Rarity.RARE, mage.cards.a.AegisOfTheLegion.class)); cards.add(new SetCardInfo("Affectionate Indrik", 155, Rarity.UNCOMMON, mage.cards.a.AffectionateIndrik.class)); cards.add(new SetCardInfo("Afterlife Insurance", 23, Rarity.UNCOMMON, mage.cards.a.AfterlifeInsurance.class)); cards.add(new SetCardInfo("Ajani's Pridemate", 52, Rarity.UNCOMMON, mage.cards.a.AjanisPridemate.class)); @@ -142,8 +143,9 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Light Up the Stage", 140, Rarity.UNCOMMON, mage.cards.l.LightUpTheStage.class)); cards.add(new SetCardInfo("Lightning Bolt", 141, Rarity.UNCOMMON, mage.cards.l.LightningBolt.class)); cards.add(new SetCardInfo("Living Lightning", 142, Rarity.UNCOMMON, mage.cards.l.LivingLightning.class)); + cards.add(new SetCardInfo("Lonis, Genetics Expert", 37, Rarity.RARE, mage.cards.l.LonisGeneticsExpert.class)); cards.add(new SetCardInfo("Lounge", 19, Rarity.UNCOMMON, mage.cards.l.Lounge.class)); - cards.add(new SetCardInfo("Martial Impetus", 65, Rarity.UNCOMMON, mage.cards.m.MartialImpetus.class)); + cards.add(new SetCardInfo("Martial Impetus", 65, Rarity.COMMON, mage.cards.m.MartialImpetus.class)); cards.add(new SetCardInfo("Masked Blackguard", 115, Rarity.COMMON, mage.cards.m.MaskedBlackguard.class)); cards.add(new SetCardInfo("Master Biomancer", 200, Rarity.MYTHIC, mage.cards.m.MasterBiomancer.class)); cards.add(new SetCardInfo("Mastermind Plum", 3, Rarity.RARE, mage.cards.m.MastermindPlum.class)); @@ -168,7 +170,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Pitiless Gorgon", 203, Rarity.COMMON, mage.cards.p.PitilessGorgon.class)); cards.add(new SetCardInfo("Plains", 254, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Predatory Impetus", 172, Rarity.COMMON, mage.cards.p.PredatoryImpetus.class)); - cards.add(new SetCardInfo("Psychic Impetus", 92, Rarity.UNCOMMON, mage.cards.p.PsychicImpetus.class)); + cards.add(new SetCardInfo("Psychic Impetus", 92, Rarity.COMMON, mage.cards.p.PsychicImpetus.class)); cards.add(new SetCardInfo("Pyrewild Shaman", 144, Rarity.UNCOMMON, mage.cards.p.PyrewildShaman.class)); cards.add(new SetCardInfo("Rakdos Carnarium", 243, Rarity.COMMON, mage.cards.r.RakdosCarnarium.class)); cards.add(new SetCardInfo("Rakdos Guildgate", 244, Rarity.COMMON, mage.cards.r.RakdosGuildgate.class)); @@ -192,6 +194,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Sage's Row Savant", 97, Rarity.COMMON, mage.cards.s.SagesRowSavant.class)); cards.add(new SetCardInfo("Sauroform Hybrid", 173, Rarity.COMMON, mage.cards.s.SauroformHybrid.class)); cards.add(new SetCardInfo("Scuttlegator", 206, Rarity.COMMON, mage.cards.s.Scuttlegator.class)); + cards.add(new SetCardInfo("Scuttling Sentinel", 42, Rarity.UNCOMMON, mage.cards.s.ScuttlingSentinel.class)); cards.add(new SetCardInfo("Secret Passage", 20, Rarity.UNCOMMON, mage.cards.s.SecretPassage.class)); cards.add(new SetCardInfo("Selesnya Guildgate", 245, Rarity.COMMON, mage.cards.s.SelesnyaGuildgate.class)); cards.add(new SetCardInfo("Selesnya Guildmage", 207, Rarity.UNCOMMON, mage.cards.s.SelesnyaGuildmage.class)); @@ -200,7 +203,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Seller of Songbirds", 72, Rarity.COMMON, mage.cards.s.SellerOfSongbirds.class)); cards.add(new SetCardInfo("Senator Peacock", 2, Rarity.RARE, mage.cards.s.SenatorPeacock.class)); cards.add(new SetCardInfo("Seraph of the Scales", 208, Rarity.MYTHIC, mage.cards.s.SeraphOfTheScales.class)); - cards.add(new SetCardInfo("Shiny Impetus", 147, Rarity.UNCOMMON, mage.cards.s.ShinyImpetus.class)); + cards.add(new SetCardInfo("Shiny Impetus", 147, Rarity.COMMON, mage.cards.s.ShinyImpetus.class)); cards.add(new SetCardInfo("Simic Growth Chamber", 247, Rarity.COMMON, mage.cards.s.SimicGrowthChamber.class)); cards.add(new SetCardInfo("Simic Guildgate", 248, Rarity.COMMON, mage.cards.s.SimicGuildgate.class)); cards.add(new SetCardInfo("Simic Signet", 228, Rarity.COMMON, mage.cards.s.SimicSignet.class)); @@ -211,6 +214,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Snow Day", 100, Rarity.UNCOMMON, mage.cards.s.SnowDay.class)); cards.add(new SetCardInfo("Spawn of Mayhem", 122, Rarity.MYTHIC, mage.cards.s.SpawnOfMayhem.class)); cards.add(new SetCardInfo("Spellgorger Weird", 148, Rarity.COMMON, mage.cards.s.SpellgorgerWeird.class)); + cards.add(new SetCardInfo("Stampede Surfer", 44, Rarity.RARE, mage.cards.s.StampedeSurfer.class)); cards.add(new SetCardInfo("Status // Statue", 210, Rarity.UNCOMMON, mage.cards.s.StatusStatue.class)); cards.add(new SetCardInfo("Steam Vents", 280, Rarity.RARE, mage.cards.s.SteamVents.class)); cards.add(new SetCardInfo("Steeple Creeper", 174, Rarity.COMMON, mage.cards.s.SteepleCreeper.class)); @@ -219,6 +223,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Street Spasm", 150, Rarity.UNCOMMON, mage.cards.s.StreetSpasm.class)); cards.add(new SetCardInfo("Study", 21, Rarity.UNCOMMON, mage.cards.s.Study.class)); cards.add(new SetCardInfo("Stunt Double", 101, Rarity.RARE, mage.cards.s.StuntDouble.class)); + cards.add(new SetCardInfo("Sumala Rumblers", 45, Rarity.UNCOMMON, mage.cards.s.SumalaRumblers.class)); cards.add(new SetCardInfo("Sunhome Stalwart", 73, Rarity.UNCOMMON, mage.cards.s.SunhomeStalwart.class)); cards.add(new SetCardInfo("Supreme Verdict", 211, Rarity.RARE, mage.cards.s.SupremeVerdict.class)); cards.add(new SetCardInfo("Swamp", 262, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); @@ -240,6 +245,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Tithebearer Giant", 124, Rarity.COMMON, mage.cards.t.TithebearerGiant.class)); cards.add(new SetCardInfo("Towering Thunderfist", 151, Rarity.COMMON, mage.cards.t.ToweringThunderfist.class)); cards.add(new SetCardInfo("Transluminant", 177, Rarity.COMMON, mage.cards.t.Transluminant.class)); + cards.add(new SetCardInfo("Tribune of Rot", 48, Rarity.UNCOMMON, mage.cards.t.TribuneOfRot.class)); cards.add(new SetCardInfo("Trostani Discordant", 213, Rarity.MYTHIC, mage.cards.t.TrostaniDiscordant.class)); cards.add(new SetCardInfo("Trusted Pegasus", 77, Rarity.COMMON, mage.cards.t.TrustedPegasus.class)); cards.add(new SetCardInfo("Turn to Frog", 103, Rarity.UNCOMMON, mage.cards.t.TurnToFrog.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index fd25f317a7b..c9d58006f2b 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -1110,6 +1110,10 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Chandra Nalaar", "1456b", Rarity.MYTHIC, mage.cards.c.ChandraNalaar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Garruk Wildspeaker", 1457, Rarity.MYTHIC, mage.cards.g.GarrukWildspeaker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Garruk Wildspeaker", "1457b", Rarity.MYTHIC, mage.cards.g.GarrukWildspeaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lara Croft, Tomb Raider", 1501, Rarity.MYTHIC, mage.cards.l.LaraCroftTombRaider.class)); + cards.add(new SetCardInfo("Rose Noble", 1580, Rarity.RARE, mage.cards.r.RoseNoble.class)); + cards.add(new SetCardInfo("The Meep", 1581, Rarity.RARE, mage.cards.t.TheMeep.class)); + cards.add(new SetCardInfo("The Fourteenth Doctor", 1583, Rarity.RARE, mage.cards.t.TheFourteenthDoctor.class)); cards.add(new SetCardInfo("Jace, the Mind Sculptor", 8001, Rarity.MYTHIC, mage.cards.j.JaceTheMindSculptor.class)); cards.add(new SetCardInfo("Garruk, Caller of Beasts", 9995, Rarity.MYTHIC, mage.cards.g.GarrukCallerOfBeasts.class)); cards.add(new SetCardInfo("Rograkh, Son of Rohgahh", 9996, Rarity.RARE, mage.cards.r.RograkhSonOfRohgahh.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairShowdown.java b/Mage.Sets/src/mage/sets/SecretLairShowdown.java new file mode 100644 index 00000000000..1c937300ffd --- /dev/null +++ b/Mage.Sets/src/mage/sets/SecretLairShowdown.java @@ -0,0 +1,46 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/slp + */ +public class SecretLairShowdown extends ExpansionSet { + + private static final SecretLairShowdown instance = new SecretLairShowdown(); + + public static SecretLairShowdown getInstance() { + return instance; + } + + private SecretLairShowdown() { + super("Secret Lair Showdown", "SLP", ExpansionSet.buildDate(2023, 2, 17), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("An Offer You Can't Refuse", 7, Rarity.RARE, mage.cards.a.AnOfferYouCantRefuse.class)); + cards.add(new SetCardInfo("Brainstorm", 1, Rarity.RARE, mage.cards.b.Brainstorm.class)); + cards.add(new SetCardInfo("Dark Ritual", 16, Rarity.RARE, mage.cards.d.DarkRitual.class)); + cards.add(new SetCardInfo("Death's Shadow", 8, Rarity.RARE, mage.cards.d.DeathsShadow.class)); + cards.add(new SetCardInfo("Dragonlord Silumgar", 9, Rarity.MYTHIC, mage.cards.d.DragonlordSilumgar.class)); + cards.add(new SetCardInfo("Eldritch Evolution", 5, Rarity.RARE, mage.cards.e.EldritchEvolution.class)); + cards.add(new SetCardInfo("Explore", 12, Rarity.RARE, mage.cards.e.Explore.class)); + cards.add(new SetCardInfo("Expressive Iteration", 13, Rarity.RARE, mage.cards.e.ExpressiveIteration.class)); + cards.add(new SetCardInfo("Fatal Push", 3, Rarity.RARE, mage.cards.f.FatalPush.class)); + cards.add(new SetCardInfo("Goblin Guide", 23, Rarity.RARE, mage.cards.g.GoblinGuide.class)); + cards.add(new SetCardInfo("Murktide Regent", 17, Rarity.MYTHIC, mage.cards.m.MurktideRegent.class)); + cards.add(new SetCardInfo("Ponder", 19, Rarity.RARE, mage.cards.p.Ponder.class)); + cards.add(new SetCardInfo("Ragavan, Nimble Pilferer", 2, Rarity.MYTHIC, mage.cards.r.RagavanNimblePilferer.class)); + cards.add(new SetCardInfo("Relentless Rats", 10, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Relentless Rats", 11, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seasoned Pyromancer", 24, Rarity.MYTHIC, mage.cards.s.SeasonedPyromancer.class)); + cards.add(new SetCardInfo("Spell Pierce", 18, Rarity.RARE, mage.cards.s.SpellPierce.class)); + cards.add(new SetCardInfo("Springleaf Drum", 22, Rarity.RARE, mage.cards.s.SpringleafDrum.class)); + cards.add(new SetCardInfo("Ugin, the Spirit Dragon", 6, Rarity.MYTHIC, mage.cards.u.UginTheSpiritDragon.class)); + cards.add(new SetCardInfo("Unholy Heat", 4, Rarity.RARE, mage.cards.u.UnholyHeat.class)); + cards.add(new SetCardInfo("Valakut, the Molten Pinnacle", 14, Rarity.RARE, mage.cards.v.ValakutTheMoltenPinnacle.class)); + cards.add(new SetCardInfo("Wrenn and Six", 15, Rarity.MYTHIC, mage.cards.w.WrennAndSix.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/StarterCommanderDecks.java b/Mage.Sets/src/mage/sets/StarterCommanderDecks.java new file mode 100644 index 00000000000..bc8c78159e1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/StarterCommanderDecks.java @@ -0,0 +1,376 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/scd + */ +public class StarterCommanderDecks extends ExpansionSet { + + private static final StarterCommanderDecks instance = new StarterCommanderDecks(); + + public static StarterCommanderDecks getInstance() { + return instance; + } + + private StarterCommanderDecks() { + super("Starter Commander Decks", "SCD", ExpansionSet.buildDate(2022, 12, 2), SetType.SUPPLEMENTAL); + this.hasBoosters = false; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Abrade", 122, Rarity.UNCOMMON, mage.cards.a.Abrade.class)); + cards.add(new SetCardInfo("Absorb", 216, Rarity.RARE, mage.cards.a.Absorb.class)); + cards.add(new SetCardInfo("Aetherize", 42, Rarity.UNCOMMON, mage.cards.a.Aetherize.class)); + cards.add(new SetCardInfo("Ajani, Caller of the Pride", 6, Rarity.MYTHIC, mage.cards.a.AjaniCallerOfThePride.class)); + cards.add(new SetCardInfo("Akoum Hellkite", 123, Rarity.RARE, mage.cards.a.AkoumHellkite.class)); + cards.add(new SetCardInfo("Akoum Refuge", 289, Rarity.UNCOMMON, mage.cards.a.AkoumRefuge.class)); + cards.add(new SetCardInfo("Ambition's Cost", 66, Rarity.UNCOMMON, mage.cards.a.AmbitionsCost.class)); + cards.add(new SetCardInfo("Angler Turtle", 43, Rarity.RARE, mage.cards.a.AnglerTurtle.class)); + cards.add(new SetCardInfo("Arcane Signet", 257, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Archfiend of Depravity", 67, Rarity.RARE, mage.cards.a.ArchfiendOfDepravity.class)); + cards.add(new SetCardInfo("Archon of Redemption", 7, Rarity.RARE, mage.cards.a.ArchonOfRedemption.class)); + cards.add(new SetCardInfo("Army of the Damned", 68, Rarity.MYTHIC, mage.cards.a.ArmyOfTheDamned.class)); + cards.add(new SetCardInfo("Atarka Monument", 258, Rarity.UNCOMMON, mage.cards.a.AtarkaMonument.class)); + cards.add(new SetCardInfo("Atarka, World Render", 1, Rarity.MYTHIC, mage.cards.a.AtarkaWorldRender.class)); + cards.add(new SetCardInfo("Aura Mutation", 217, Rarity.RARE, mage.cards.a.AuraMutation.class)); + cards.add(new SetCardInfo("Avacyn's Pilgrim", 171, Rarity.COMMON, mage.cards.a.AvacynsPilgrim.class)); + cards.add(new SetCardInfo("Aven Gagglemaster", 8, Rarity.UNCOMMON, mage.cards.a.AvenGagglemaster.class)); + cards.add(new SetCardInfo("Azorius Signet", 259, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class)); + cards.add(new SetCardInfo("Banishing Light", 9, Rarity.UNCOMMON, mage.cards.b.BanishingLight.class)); + cards.add(new SetCardInfo("Beast Within", 172, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); + cards.add(new SetCardInfo("Bident of Thassa", 44, Rarity.RARE, mage.cards.b.BidentOfThassa.class)); + cards.add(new SetCardInfo("Blasphemous Act", 124, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); + cards.add(new SetCardInfo("Bloodfell Caves", 290, Rarity.COMMON, mage.cards.b.BloodfellCaves.class)); + cards.add(new SetCardInfo("Bloodgift Demon", 69, Rarity.RARE, mage.cards.b.BloodgiftDemon.class)); + cards.add(new SetCardInfo("Blossoming Defense", 173, Rarity.UNCOMMON, mage.cards.b.BlossomingDefense.class)); + cards.add(new SetCardInfo("Blossoming Sands", 291, Rarity.COMMON, mage.cards.b.BlossomingSands.class)); + cards.add(new SetCardInfo("Brash Taunter", 125, Rarity.RARE, mage.cards.b.BrashTaunter.class)); + cards.add(new SetCardInfo("Breath of Malfegor", 218, Rarity.COMMON, mage.cards.b.BreathOfMalfegor.class)); + cards.add(new SetCardInfo("Burnished Hart", 260, Rarity.UNCOMMON, mage.cards.b.BurnishedHart.class)); + cards.add(new SetCardInfo("Camaraderie", 219, Rarity.RARE, mage.cards.c.Camaraderie.class)); + cards.add(new SetCardInfo("Canopy Vista", 292, Rarity.RARE, mage.cards.c.CanopyVista.class)); + cards.add(new SetCardInfo("Cartographer's Hawk", 10, Rarity.RARE, mage.cards.c.CartographersHawk.class)); + cards.add(new SetCardInfo("Cemetery Reaper", 70, Rarity.RARE, mage.cards.c.CemeteryReaper.class)); + cards.add(new SetCardInfo("Chain Reaction", 126, Rarity.RARE, mage.cards.c.ChainReaction.class)); + cards.add(new SetCardInfo("Champion of Lambholt", 174, Rarity.RARE, mage.cards.c.ChampionOfLambholt.class)); + cards.add(new SetCardInfo("Champion of the Perished", 71, Rarity.RARE, mage.cards.c.ChampionOfThePerished.class)); + cards.add(new SetCardInfo("Chaos Warp", 127, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Choked Estuary", 293, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); + cards.add(new SetCardInfo("Cinder Barrens", 294, Rarity.UNCOMMON, mage.cards.c.CinderBarrens.class)); + cards.add(new SetCardInfo("Cinder Glade", 295, Rarity.RARE, mage.cards.c.CinderGlade.class)); + cards.add(new SetCardInfo("Citanul Hierophants", 175, Rarity.RARE, mage.cards.c.CitanulHierophants.class)); + cards.add(new SetCardInfo("Citywide Bust", 11, Rarity.RARE, mage.cards.c.CitywideBust.class)); + cards.add(new SetCardInfo("Clan Defiance", 220, Rarity.RARE, mage.cards.c.ClanDefiance.class)); + cards.add(new SetCardInfo("Cleansing Nova", 12, Rarity.RARE, mage.cards.c.CleansingNova.class)); + cards.add(new SetCardInfo("Cloudblazer", 221, Rarity.UNCOMMON, mage.cards.c.Cloudblazer.class)); + cards.add(new SetCardInfo("Coastal Tower", 296, Rarity.UNCOMMON, mage.cards.c.CoastalTower.class)); + cards.add(new SetCardInfo("Collective Blessing", 222, Rarity.RARE, mage.cards.c.CollectiveBlessing.class)); + cards.add(new SetCardInfo("Collective Unconscious", 176, Rarity.RARE, mage.cards.c.CollectiveUnconscious.class)); + cards.add(new SetCardInfo("Combustible Gearhulk", 128, Rarity.MYTHIC, mage.cards.c.CombustibleGearhulk.class)); + cards.add(new SetCardInfo("Commander's Insignia", 13, Rarity.RARE, mage.cards.c.CommandersInsignia.class)); + cards.add(new SetCardInfo("Commander's Sphere", 261, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); + cards.add(new SetCardInfo("Command Tower", 297, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Conclave Tribunal", 14, Rarity.UNCOMMON, mage.cards.c.ConclaveTribunal.class)); + cards.add(new SetCardInfo("Condemn", 15, Rarity.UNCOMMON, mage.cards.c.Condemn.class)); + cards.add(new SetCardInfo("Counterspell", 45, Rarity.COMMON, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Coveted Jewel", 262, Rarity.RARE, mage.cards.c.CovetedJewel.class)); + cards.add(new SetCardInfo("Crippling Fear", 72, Rarity.RARE, mage.cards.c.CripplingFear.class)); + cards.add(new SetCardInfo("Crucible of Fire", 129, Rarity.RARE, mage.cards.c.CrucibleOfFire.class)); + cards.add(new SetCardInfo("Cruel Revival", 73, Rarity.UNCOMMON, mage.cards.c.CruelRevival.class)); + cards.add(new SetCardInfo("Crush Contraband", 16, Rarity.UNCOMMON, mage.cards.c.CrushContraband.class)); + cards.add(new SetCardInfo("Cultivate", 177, Rarity.UNCOMMON, mage.cards.c.Cultivate.class)); + cards.add(new SetCardInfo("Curse of Bounty", 178, Rarity.UNCOMMON, mage.cards.c.CurseOfBounty.class)); + cards.add(new SetCardInfo("Curse of Disturbance", 74, Rarity.UNCOMMON, mage.cards.c.CurseOfDisturbance.class)); + cards.add(new SetCardInfo("Dauntless Escort", 223, Rarity.RARE, mage.cards.d.DauntlessEscort.class)); + cards.add(new SetCardInfo("Dawn of Hope", 17, Rarity.RARE, mage.cards.d.DawnOfHope.class)); + cards.add(new SetCardInfo("Deadly Tempest", 75, Rarity.RARE, mage.cards.d.DeadlyTempest.class)); + cards.add(new SetCardInfo("Deep Analysis", 46, Rarity.COMMON, mage.cards.d.DeepAnalysis.class)); + cards.add(new SetCardInfo("Demanding Dragon", 130, Rarity.RARE, mage.cards.d.DemandingDragon.class)); + cards.add(new SetCardInfo("Devouring Light", 18, Rarity.UNCOMMON, mage.cards.d.DevouringLight.class)); + cards.add(new SetCardInfo("Dictate of Heliod", 19, Rarity.RARE, mage.cards.d.DictateOfHeliod.class)); + cards.add(new SetCardInfo("Dictate of the Twin Gods", 131, Rarity.RARE, mage.cards.d.DictateOfTheTwinGods.class)); + cards.add(new SetCardInfo("Diluvian Primordial", 47, Rarity.RARE, mage.cards.d.DiluvianPrimordial.class)); + cards.add(new SetCardInfo("Dimir Signet", 263, Rarity.UNCOMMON, mage.cards.d.DimirSignet.class)); + cards.add(new SetCardInfo("Diregraf Captain", 224, Rarity.UNCOMMON, mage.cards.d.DiregrafCaptain.class)); + cards.add(new SetCardInfo("Disenchant", 20, Rarity.COMMON, mage.cards.d.Disenchant.class)); + cards.add(new SetCardInfo("Dismal Backwater", 298, Rarity.COMMON, mage.cards.d.DismalBackwater.class)); + cards.add(new SetCardInfo("Distant Melody", 48, Rarity.COMMON, mage.cards.d.DistantMelody.class)); + cards.add(new SetCardInfo("Draconic Disciple", 225, Rarity.UNCOMMON, mage.cards.d.DraconicDisciple.class)); + cards.add(new SetCardInfo("Dragonkin Berserker", 134, Rarity.RARE, mage.cards.d.DragonkinBerserker.class)); + cards.add(new SetCardInfo("Dragonlord's Servant", 135, Rarity.UNCOMMON, mage.cards.d.DragonlordsServant.class)); + cards.add(new SetCardInfo("Dragon Mage", 132, Rarity.UNCOMMON, mage.cards.d.DragonMage.class)); + cards.add(new SetCardInfo("Dragonmaster Outcast", 136, Rarity.MYTHIC, mage.cards.d.DragonmasterOutcast.class)); + cards.add(new SetCardInfo("Dragon's Hoard", 264, Rarity.RARE, mage.cards.d.DragonsHoard.class)); + cards.add(new SetCardInfo("Dragonspeaker Shaman", 137, Rarity.UNCOMMON, mage.cards.d.DragonspeakerShaman.class)); + cards.add(new SetCardInfo("Dragon Tempest", 133, Rarity.UNCOMMON, mage.cards.d.DragonTempest.class)); + cards.add(new SetCardInfo("Drakuseth, Maw of Flames", 138, Rarity.RARE, mage.cards.d.DrakusethMawOfFlames.class)); + cards.add(new SetCardInfo("Dream Pillager", 139, Rarity.RARE, mage.cards.d.DreamPillager.class)); + cards.add(new SetCardInfo("Dredge the Mire", 76, Rarity.RARE, mage.cards.d.DredgeTheMire.class)); + cards.add(new SetCardInfo("Drumhunter", 179, Rarity.UNCOMMON, mage.cards.d.Drumhunter.class)); + cards.add(new SetCardInfo("Elemental Bond", 180, Rarity.UNCOMMON, mage.cards.e.ElementalBond.class)); + cards.add(new SetCardInfo("Elfhame Palace", 299, Rarity.UNCOMMON, mage.cards.e.ElfhamePalace.class)); + cards.add(new SetCardInfo("Emeria Angel", 21, Rarity.RARE, mage.cards.e.EmeriaAngel.class)); + cards.add(new SetCardInfo("Emmara, Soul of the Accord", 2, Rarity.MYTHIC, mage.cards.e.EmmaraSoulOfTheAccord.class)); + cards.add(new SetCardInfo("Empyrean Eagle", 226, Rarity.UNCOMMON, mage.cards.e.EmpyreanEagle.class)); + cards.add(new SetCardInfo("Enter the God-Eternals", 227, Rarity.RARE, mage.cards.e.EnterTheGodEternals.class)); + cards.add(new SetCardInfo("Eternal Skylord", 49, Rarity.UNCOMMON, mage.cards.e.EternalSkylord.class)); + cards.add(new SetCardInfo("Eternal Witness", 181, Rarity.UNCOMMON, mage.cards.e.EternalWitness.class)); + cards.add(new SetCardInfo("Ever-Watching Threshold", 50, Rarity.RARE, mage.cards.e.EverWatchingThreshold.class)); + cards.add(new SetCardInfo("Explosion of Riches", 140, Rarity.UNCOMMON, mage.cards.e.ExplosionOfRiches.class)); + cards.add(new SetCardInfo("Faerie Formation", 51, Rarity.RARE, mage.cards.f.FaerieFormation.class)); + cards.add(new SetCardInfo("Farhaven Elf", 182, Rarity.COMMON, mage.cards.f.FarhavenElf.class)); + cards.add(new SetCardInfo("Favorable Winds", 52, Rarity.UNCOMMON, mage.cards.f.FavorableWinds.class)); + cards.add(new SetCardInfo("Feed the Swarm", 77, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); + cards.add(new SetCardInfo("Felidar Retreat", 22, Rarity.RARE, mage.cards.f.FelidarRetreat.class)); + cards.add(new SetCardInfo("Fiery Confluence", 141, Rarity.RARE, mage.cards.f.FieryConfluence.class)); + cards.add(new SetCardInfo("Fires of Yavimaya", 228, Rarity.UNCOMMON, mage.cards.f.FiresOfYavimaya.class)); + cards.add(new SetCardInfo("Flameblast Dragon", 142, Rarity.RARE, mage.cards.f.FlameblastDragon.class)); + cards.add(new SetCardInfo("Fleshbag Marauder", 78, Rarity.COMMON, mage.cards.f.FleshbagMarauder.class)); + cards.add(new SetCardInfo("Foe-Razer Regent", 183, Rarity.RARE, mage.cards.f.FoeRazerRegent.class)); + cards.add(new SetCardInfo("Foreboding Ruins", 300, Rarity.RARE, mage.cards.f.ForebodingRuins.class)); + cards.add(new SetCardInfo("Forest", 349, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 350, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 351, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 352, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fortified Village", 301, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); + cards.add(new SetCardInfo("Frontier Siege", 184, Rarity.RARE, mage.cards.f.FrontierSiege.class)); + cards.add(new SetCardInfo("Furnace Whelp", 143, Rarity.UNCOMMON, mage.cards.f.FurnaceWhelp.class)); + cards.add(new SetCardInfo("Game Trail", 302, Rarity.RARE, mage.cards.g.GameTrail.class)); + cards.add(new SetCardInfo("Garruk's Uprising", 185, Rarity.UNCOMMON, mage.cards.g.GarruksUprising.class)); + cards.add(new SetCardInfo("Generous Gift", 23, Rarity.UNCOMMON, mage.cards.g.GenerousGift.class)); + cards.add(new SetCardInfo("Geode Rager", 144, Rarity.RARE, mage.cards.g.GeodeRager.class)); + cards.add(new SetCardInfo("Geralf's Mindcrusher", 53, Rarity.RARE, mage.cards.g.GeralfsMindcrusher.class)); + cards.add(new SetCardInfo("Gideon Jura", 24, Rarity.MYTHIC, mage.cards.g.GideonJura.class)); + cards.add(new SetCardInfo("Gisa and Geralf", 3, Rarity.MYTHIC, mage.cards.g.GisaAndGeralf.class)); + cards.add(new SetCardInfo("Gleaming Overseer", 229, Rarity.UNCOMMON, mage.cards.g.GleamingOverseer.class)); + cards.add(new SetCardInfo("Gravespawn Sovereign", 79, Rarity.RARE, mage.cards.g.GravespawnSovereign.class)); + cards.add(new SetCardInfo("Gravitational Shift", 54, Rarity.RARE, mage.cards.g.GravitationalShift.class)); + cards.add(new SetCardInfo("Gray Merchant of Asphodel", 80, Rarity.UNCOMMON, mage.cards.g.GrayMerchantOfAsphodel.class)); + cards.add(new SetCardInfo("Graypelt Refuge", 303, Rarity.UNCOMMON, mage.cards.g.GraypeltRefuge.class)); + cards.add(new SetCardInfo("Great Oak Guardian", 186, Rarity.UNCOMMON, mage.cards.g.GreatOakGuardian.class)); + cards.add(new SetCardInfo("Grimoire of the Dead", 265, Rarity.MYTHIC, mage.cards.g.GrimoireOfTheDead.class)); + cards.add(new SetCardInfo("Guttersnipe", 145, Rarity.UNCOMMON, mage.cards.g.Guttersnipe.class)); + cards.add(new SetCardInfo("Hanged Executioner", 25, Rarity.RARE, mage.cards.h.HangedExecutioner.class)); + cards.add(new SetCardInfo("Harbinger of the Hunt", 230, Rarity.RARE, mage.cards.h.HarbingerOfTheHunt.class)); + cards.add(new SetCardInfo("Harmonize", 187, Rarity.UNCOMMON, mage.cards.h.Harmonize.class)); + cards.add(new SetCardInfo("Harvest Season", 188, Rarity.RARE, mage.cards.h.HarvestSeason.class)); + cards.add(new SetCardInfo("Hate Mirage", 146, Rarity.UNCOMMON, mage.cards.h.HateMirage.class)); + cards.add(new SetCardInfo("Havengul Lich", 231, Rarity.MYTHIC, mage.cards.h.HavengulLich.class)); + cards.add(new SetCardInfo("Haven of the Spirit Dragon", 304, Rarity.RARE, mage.cards.h.HavenOfTheSpiritDragon.class)); + cards.add(new SetCardInfo("Hedron Archive", 266, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class)); + cards.add(new SetCardInfo("Heraldic Banner", 267, Rarity.UNCOMMON, mage.cards.h.HeraldicBanner.class)); + cards.add(new SetCardInfo("Hoard-Smelter Dragon", 147, Rarity.RARE, mage.cards.h.HoardSmelterDragon.class)); + cards.add(new SetCardInfo("Holdout Settlement", 305, Rarity.COMMON, mage.cards.h.HoldoutSettlement.class)); + cards.add(new SetCardInfo("Hornet Nest", 189, Rarity.RARE, mage.cards.h.HornetNest.class)); + cards.add(new SetCardInfo("Hornet Queen", 190, Rarity.RARE, mage.cards.h.HornetQueen.class)); + cards.add(new SetCardInfo("Hour of Reckoning", 26, Rarity.RARE, mage.cards.h.HourOfReckoning.class)); + cards.add(new SetCardInfo("Hunter's Insight", 191, Rarity.UNCOMMON, mage.cards.h.HuntersInsight.class)); + cards.add(new SetCardInfo("Hunter's Prowess", 192, Rarity.RARE, mage.cards.h.HuntersProwess.class)); + cards.add(new SetCardInfo("Idol of Oblivion", 268, Rarity.RARE, mage.cards.i.IdolOfOblivion.class)); + cards.add(new SetCardInfo("Indulgent Tormentor", 81, Rarity.UNCOMMON, mage.cards.i.IndulgentTormentor.class)); + cards.add(new SetCardInfo("Inspired Sphinx", 55, Rarity.MYTHIC, mage.cards.i.InspiredSphinx.class)); + cards.add(new SetCardInfo("Island", 337, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 338, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 339, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 340, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Isperia, Supreme Judge", 4, Rarity.MYTHIC, mage.cards.i.IsperiaSupremeJudge.class)); + cards.add(new SetCardInfo("Jade Mage", 193, Rarity.UNCOMMON, mage.cards.j.JadeMage.class)); + cards.add(new SetCardInfo("Jaspera Sentinel", 194, Rarity.COMMON, mage.cards.j.JasperaSentinel.class)); + cards.add(new SetCardInfo("Josu Vess, Lich Knight", 82, Rarity.RARE, mage.cards.j.JosuVessLichKnight.class)); + cards.add(new SetCardInfo("Jubilant Skybonder", 232, Rarity.UNCOMMON, mage.cards.j.JubilantSkybonder.class)); + cards.add(new SetCardInfo("Jwar Isle Refuge", 306, Rarity.UNCOMMON, mage.cards.j.JwarIsleRefuge.class)); + cards.add(new SetCardInfo("Kaervek the Merciless", 233, Rarity.RARE, mage.cards.k.KaervekTheMerciless.class)); + cards.add(new SetCardInfo("Kangee, Sky Warden", 234, Rarity.UNCOMMON, mage.cards.k.KangeeSkyWarden.class)); + cards.add(new SetCardInfo("Kangee's Lieutenant", 27, Rarity.UNCOMMON, mage.cards.k.KangeesLieutenant.class)); + cards.add(new SetCardInfo("Karametra's Favor", 195, Rarity.UNCOMMON, mage.cards.k.KarametrasFavor.class)); + cards.add(new SetCardInfo("Kardur, Doomscourge", 5, Rarity.MYTHIC, mage.cards.k.KardurDoomscourge.class)); + cards.add(new SetCardInfo("Kazandu Refuge", 307, Rarity.UNCOMMON, mage.cards.k.KazanduRefuge.class)); + cards.add(new SetCardInfo("Kazuul, Tyrant of the Cliffs", 148, Rarity.RARE, mage.cards.k.KazuulTyrantOfTheCliffs.class)); + cards.add(new SetCardInfo("Laboratory Drudge", 56, Rarity.RARE, mage.cards.l.LaboratoryDrudge.class)); + cards.add(new SetCardInfo("Lazotep Plating", 57, Rarity.UNCOMMON, mage.cards.l.LazotepPlating.class)); + cards.add(new SetCardInfo("Lazotep Reaver", 83, Rarity.COMMON, mage.cards.l.LazotepReaver.class)); + cards.add(new SetCardInfo("Leafkin Druid", 196, Rarity.COMMON, mage.cards.l.LeafkinDruid.class)); + cards.add(new SetCardInfo("Lightning Greaves", 269, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); + cards.add(new SetCardInfo("Liliana's Devotee", 85, Rarity.UNCOMMON, mage.cards.l.LilianasDevotee.class)); + cards.add(new SetCardInfo("Liliana's Mastery", 86, Rarity.RARE, mage.cards.l.LilianasMastery.class)); + cards.add(new SetCardInfo("Liliana's Standard Bearer", 87, Rarity.RARE, mage.cards.l.LilianasStandardBearer.class)); + cards.add(new SetCardInfo("Liliana, Untouched by Death", 84, Rarity.MYTHIC, mage.cards.l.LilianaUntouchedByDeath.class)); + cards.add(new SetCardInfo("Loaming Shaman", 197, Rarity.UNCOMMON, mage.cards.l.LoamingShaman.class)); + cards.add(new SetCardInfo("Lord of the Accursed", 88, Rarity.UNCOMMON, mage.cards.l.LordOfTheAccursed.class)); + cards.add(new SetCardInfo("Lotleth Giant", 89, Rarity.UNCOMMON, mage.cards.l.LotlethGiant.class)); + cards.add(new SetCardInfo("Loyal Guardian", 198, Rarity.UNCOMMON, mage.cards.l.LoyalGuardian.class)); + cards.add(new SetCardInfo("Loyal Subordinate", 90, Rarity.UNCOMMON, mage.cards.l.LoyalSubordinate.class)); + cards.add(new SetCardInfo("Magmaquake", 149, Rarity.RARE, mage.cards.m.Magmaquake.class)); + cards.add(new SetCardInfo("Magmatic Force", 150, Rarity.RARE, mage.cards.m.MagmaticForce.class)); + cards.add(new SetCardInfo("Maja, Bretagard Protector", 235, Rarity.UNCOMMON, mage.cards.m.MajaBretagardProtector.class)); + cards.add(new SetCardInfo("Mana Geyser", 151, Rarity.COMMON, mage.cards.m.ManaGeyser.class)); + cards.add(new SetCardInfo("March of the Multitudes", 236, Rarity.MYTHIC, mage.cards.m.MarchOfTheMultitudes.class)); + cards.add(new SetCardInfo("Meandering River", 308, Rarity.UNCOMMON, mage.cards.m.MeanderingRiver.class)); + cards.add(new SetCardInfo("Mentor of the Meek", 28, Rarity.RARE, mage.cards.m.MentorOfTheMeek.class)); + cards.add(new SetCardInfo("Midnight Reaper", 91, Rarity.RARE, mage.cards.m.MidnightReaper.class)); + cards.add(new SetCardInfo("Migratory Route", 237, Rarity.UNCOMMON, mage.cards.m.MigratoryRoute.class)); + cards.add(new SetCardInfo("Mire Triton", 92, Rarity.UNCOMMON, mage.cards.m.MireTriton.class)); + cards.add(new SetCardInfo("Molten Slagheap", 309, Rarity.UNCOMMON, mage.cards.m.MoltenSlagheap.class)); + cards.add(new SetCardInfo("Moorland Haunt", 310, Rarity.RARE, mage.cards.m.MoorlandHaunt.class)); + cards.add(new SetCardInfo("Mordant Dragon", 152, Rarity.RARE, mage.cards.m.MordantDragon.class)); + cards.add(new SetCardInfo("Mountain", 345, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 346, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 347, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 348, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Murder", 93, Rarity.COMMON, mage.cards.m.Murder.class)); + cards.add(new SetCardInfo("Myriad Landscape", 311, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); + cards.add(new SetCardInfo("Necromantic Selection", 94, Rarity.RARE, mage.cards.n.NecromanticSelection.class)); + cards.add(new SetCardInfo("Necrotic Hex", 95, Rarity.RARE, mage.cards.n.NecroticHex.class)); + cards.add(new SetCardInfo("Negate", 58, Rarity.COMMON, mage.cards.n.Negate.class)); + cards.add(new SetCardInfo("Nihil Spellbomb", 270, Rarity.COMMON, mage.cards.n.NihilSpellbomb.class)); + cards.add(new SetCardInfo("Nissa's Expedition", 199, Rarity.UNCOMMON, mage.cards.n.NissasExpedition.class)); + cards.add(new SetCardInfo("Nullmage Shepherd", 200, Rarity.UNCOMMON, mage.cards.n.NullmageShepherd.class)); + cards.add(new SetCardInfo("Ob Nixilis Reignited", 96, Rarity.MYTHIC, mage.cards.o.ObNixilisReignited.class)); + cards.add(new SetCardInfo("Open the Graves", 97, Rarity.RARE, mage.cards.o.OpenTheGraves.class)); + cards.add(new SetCardInfo("Overrun", 201, Rarity.UNCOMMON, mage.cards.o.Overrun.class)); + cards.add(new SetCardInfo("Overseer of the Damned", 98, Rarity.RARE, mage.cards.o.OverseerOfTheDamned.class)); + cards.add(new SetCardInfo("Overwhelming Instinct", 202, Rarity.UNCOMMON, mage.cards.o.OverwhelmingInstinct.class)); + cards.add(new SetCardInfo("Path of Ancestry", 312, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); + cards.add(new SetCardInfo("Path to Exile", 29, Rarity.UNCOMMON, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Pilfered Plans", 238, Rarity.COMMON, mage.cards.p.PilferedPlans.class)); + cards.add(new SetCardInfo("Pilgrim's Eye", 271, Rarity.COMMON, mage.cards.p.PilgrimsEye.class)); + cards.add(new SetCardInfo("Plains", 333, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 334, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 335, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 336, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Port Town", 313, Rarity.RARE, mage.cards.p.PortTown.class)); + cards.add(new SetCardInfo("Prairie Stream", 314, Rarity.RARE, mage.cards.p.PrairieStream.class)); + cards.add(new SetCardInfo("Presence of Gond", 203, Rarity.COMMON, mage.cards.p.PresenceOfGond.class)); + cards.add(new SetCardInfo("Primal Might", 204, Rarity.RARE, mage.cards.p.PrimalMight.class)); + cards.add(new SetCardInfo("Profane Command", 99, Rarity.RARE, mage.cards.p.ProfaneCommand.class)); + cards.add(new SetCardInfo("Provoke the Trolls", 153, Rarity.UNCOMMON, mage.cards.p.ProvokeTheTrolls.class)); + cards.add(new SetCardInfo("Rakdos Charm", 239, Rarity.UNCOMMON, mage.cards.r.RakdosCharm.class)); + cards.add(new SetCardInfo("Rakdos Signet", 272, Rarity.UNCOMMON, mage.cards.r.RakdosSignet.class)); + cards.add(new SetCardInfo("Rakshasa Debaser", 100, Rarity.RARE, mage.cards.r.RakshasaDebaser.class)); + cards.add(new SetCardInfo("Rally of Wings", 30, Rarity.UNCOMMON, mage.cards.r.RallyOfWings.class)); + cards.add(new SetCardInfo("Rapacious Dragon", 154, Rarity.COMMON, mage.cards.r.RapaciousDragon.class)); + cards.add(new SetCardInfo("Read the Bones", 101, Rarity.COMMON, mage.cards.r.ReadTheBones.class)); + cards.add(new SetCardInfo("Reclamation Sage", 205, Rarity.UNCOMMON, mage.cards.r.ReclamationSage.class)); + cards.add(new SetCardInfo("Reign of the Pit", 102, Rarity.RARE, mage.cards.r.ReignOfThePit.class)); + cards.add(new SetCardInfo("Remorseful Cleric", 31, Rarity.RARE, mage.cards.r.RemorsefulCleric.class)); + cards.add(new SetCardInfo("Return to Nature", 206, Rarity.COMMON, mage.cards.r.ReturnToNature.class)); + cards.add(new SetCardInfo("Rishkar, Peema Renegade", 207, Rarity.RARE, mage.cards.r.RishkarPeemaRenegade.class)); + cards.add(new SetCardInfo("Rootborn Defenses", 32, Rarity.COMMON, mage.cards.r.RootbornDefenses.class)); + cards.add(new SetCardInfo("Rugged Highlands", 315, Rarity.COMMON, mage.cards.r.RuggedHighlands.class)); + cards.add(new SetCardInfo("Runehorn Hellkite", 155, Rarity.RARE, mage.cards.r.RunehornHellkite.class)); + cards.add(new SetCardInfo("Sakura-Tribe Elder", 208, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class)); + cards.add(new SetCardInfo("Salt Marsh", 316, Rarity.UNCOMMON, mage.cards.s.SaltMarsh.class)); + cards.add(new SetCardInfo("Sangromancer", 103, Rarity.RARE, mage.cards.s.Sangromancer.class)); + cards.add(new SetCardInfo("Sarkhan, the Dragonspeaker", 156, Rarity.MYTHIC, mage.cards.s.SarkhanTheDragonspeaker.class)); + cards.add(new SetCardInfo("Savage Ventmaw", 240, Rarity.UNCOMMON, mage.cards.s.SavageVentmaw.class)); + cards.add(new SetCardInfo("Scatter the Seeds", 209, Rarity.COMMON, mage.cards.s.ScatterTheSeeds.class)); + cards.add(new SetCardInfo("Scavenging Ooze", 210, Rarity.RARE, mage.cards.s.ScavengingOoze.class)); + cards.add(new SetCardInfo("Scourge of Nel Toth", 104, Rarity.RARE, mage.cards.s.ScourgeOfNelToth.class)); + cards.add(new SetCardInfo("Scourge of Valkas", 157, Rarity.MYTHIC, mage.cards.s.ScourgeOfValkas.class)); + cards.add(new SetCardInfo("Scythe Specter", 105, Rarity.RARE, mage.cards.s.ScytheSpecter.class)); + cards.add(new SetCardInfo("Sejiri Refuge", 317, Rarity.UNCOMMON, mage.cards.s.SejiriRefuge.class)); + cards.add(new SetCardInfo("Selesnya Evangel", 241, Rarity.COMMON, mage.cards.s.SelesnyaEvangel.class)); + cards.add(new SetCardInfo("Selesnya Guildmage", 242, Rarity.UNCOMMON, mage.cards.s.SelesnyaGuildmage.class)); + cards.add(new SetCardInfo("Sephara, Sky's Blade", 33, Rarity.RARE, mage.cards.s.SepharaSkysBlade.class)); + cards.add(new SetCardInfo("Sepulchral Primordial", 106, Rarity.RARE, mage.cards.s.SepulchralPrimordial.class)); + cards.add(new SetCardInfo("Shamanic Revelation", 211, Rarity.RARE, mage.cards.s.ShamanicRevelation.class)); + cards.add(new SetCardInfo("Sharding Sphinx", 59, Rarity.RARE, mage.cards.s.ShardingSphinx.class)); + cards.add(new SetCardInfo("Shivan Oasis", 318, Rarity.UNCOMMON, mage.cards.s.ShivanOasis.class)); + cards.add(new SetCardInfo("Sign in Blood", 107, Rarity.COMMON, mage.cards.s.SignInBlood.class)); + cards.add(new SetCardInfo("Sinister Sabotage", 60, Rarity.UNCOMMON, mage.cards.s.SinisterSabotage.class)); + cards.add(new SetCardInfo("Skycat Sovereign", 243, Rarity.RARE, mage.cards.s.SkycatSovereign.class)); + cards.add(new SetCardInfo("Sky Diamond", 273, Rarity.COMMON, mage.cards.s.SkyDiamond.class)); + cards.add(new SetCardInfo("Skyscanner", 274, Rarity.COMMON, mage.cards.s.Skyscanner.class)); + cards.add(new SetCardInfo("Slate of Ancestry", 275, Rarity.RARE, mage.cards.s.SlateOfAncestry.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 319, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Solemn Simulacrum", 277, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); + cards.add(new SetCardInfo("Sol Ring", 276, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); + cards.add(new SetCardInfo("Soul Shatter", 108, Rarity.RARE, mage.cards.s.SoulShatter.class)); + cards.add(new SetCardInfo("Soul Snare", 34, Rarity.UNCOMMON, mage.cards.s.SoulSnare.class)); + cards.add(new SetCardInfo("Spark Reaper", 109, Rarity.COMMON, mage.cards.s.SparkReaper.class)); + cards.add(new SetCardInfo("Sphinx of Enlightenment", 61, Rarity.MYTHIC, mage.cards.s.SphinxOfEnlightenment.class)); + cards.add(new SetCardInfo("Sphinx's Revelation", 244, Rarity.MYTHIC, mage.cards.s.SphinxsRevelation.class)); + cards.add(new SetCardInfo("Spiteful Visions", 245, Rarity.RARE, mage.cards.s.SpitefulVisions.class)); + cards.add(new SetCardInfo("Spit Flame", 158, Rarity.RARE, mage.cards.s.SpitFlame.class)); + cards.add(new SetCardInfo("Sporemound", 212, Rarity.COMMON, mage.cards.s.Sporemound.class)); + cards.add(new SetCardInfo("Staggering Insight", 246, Rarity.UNCOMMON, mage.cards.s.StaggeringInsight.class)); + cards.add(new SetCardInfo("Steel Hellkite", 278, Rarity.RARE, mage.cards.s.SteelHellkite.class)); + cards.add(new SetCardInfo("Steel-Plume Marshal", 35, Rarity.RARE, mage.cards.s.SteelPlumeMarshal.class)); + cards.add(new SetCardInfo("Stensia Bloodhall", 320, Rarity.RARE, mage.cards.s.StensiaBloodhall.class)); + cards.add(new SetCardInfo("Stormfist Crusader", 247, Rarity.RARE, mage.cards.s.StormfistCrusader.class)); + cards.add(new SetCardInfo("Storm Herd", 36, Rarity.RARE, mage.cards.s.StormHerd.class)); + cards.add(new SetCardInfo("Submerged Boneyard", 321, Rarity.UNCOMMON, mage.cards.s.SubmergedBoneyard.class)); + cards.add(new SetCardInfo("Sunbird's Invocation", 159, Rarity.RARE, mage.cards.s.SunbirdsInvocation.class)); + cards.add(new SetCardInfo("Sunken Hollow", 322, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Swamp", 341, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 342, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 343, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 344, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sweltering Suns", 160, Rarity.RARE, mage.cards.s.SwelteringSuns.class)); + cards.add(new SetCardInfo("Swiftfoot Boots", 279, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Swords to Plowshares", 37, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Sylvan Reclamation", 248, Rarity.UNCOMMON, mage.cards.s.SylvanReclamation.class)); + cards.add(new SetCardInfo("Syphon Flesh", 110, Rarity.UNCOMMON, mage.cards.s.SyphonFlesh.class)); + cards.add(new SetCardInfo("Syphon Mind", 111, Rarity.COMMON, mage.cards.s.SyphonMind.class)); + cards.add(new SetCardInfo("Talisman of Dominance", 280, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); + cards.add(new SetCardInfo("Talisman of Impulse", 281, Rarity.UNCOMMON, mage.cards.t.TalismanOfImpulse.class)); + cards.add(new SetCardInfo("Talisman of Indulgence", 282, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class)); + cards.add(new SetCardInfo("Talisman of Progress", 283, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class)); + cards.add(new SetCardInfo("Talisman of Unity", 284, Rarity.UNCOMMON, mage.cards.t.TalismanOfUnity.class)); + cards.add(new SetCardInfo("Tectonic Giant", 161, Rarity.RARE, mage.cards.t.TectonicGiant.class)); + cards.add(new SetCardInfo("Temple of Abandon", 323, Rarity.RARE, mage.cards.t.TempleOfAbandon.class)); + cards.add(new SetCardInfo("Temple of Deceit", 324, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); + cards.add(new SetCardInfo("Temple of Enlightenment", 325, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); + cards.add(new SetCardInfo("Temple of Malice", 326, Rarity.RARE, mage.cards.t.TempleOfMalice.class)); + cards.add(new SetCardInfo("Temple of Plenty", 327, Rarity.RARE, mage.cards.t.TempleOfPlenty.class)); + cards.add(new SetCardInfo("Terminate", 249, Rarity.UNCOMMON, mage.cards.t.Terminate.class)); + cards.add(new SetCardInfo("Theater of Horrors", 250, Rarity.RARE, mage.cards.t.TheaterOfHorrors.class)); + cards.add(new SetCardInfo("Thermo-Alchemist", 162, Rarity.COMMON, mage.cards.t.ThermoAlchemist.class)); + cards.add(new SetCardInfo("Thought Vessel", 285, Rarity.UNCOMMON, mage.cards.t.ThoughtVessel.class)); + cards.add(new SetCardInfo("Thunderbreak Regent", 163, Rarity.RARE, mage.cards.t.ThunderbreakRegent.class)); + cards.add(new SetCardInfo("Thunderclap Wyvern", 251, Rarity.UNCOMMON, mage.cards.t.ThunderclapWyvern.class)); + cards.add(new SetCardInfo("Thunderfoot Baloth", 213, Rarity.RARE, mage.cards.t.ThunderfootBaloth.class)); + cards.add(new SetCardInfo("Thundermaw Hellkite", 164, Rarity.MYTHIC, mage.cards.t.ThundermawHellkite.class)); + cards.add(new SetCardInfo("Tide Skimmer", 62, Rarity.UNCOMMON, mage.cards.t.TideSkimmer.class)); + cards.add(new SetCardInfo("Timber Gorge", 328, Rarity.UNCOMMON, mage.cards.t.TimberGorge.class)); + cards.add(new SetCardInfo("Time Wipe", 252, Rarity.RARE, mage.cards.t.TimeWipe.class)); + cards.add(new SetCardInfo("Titan Hunter", 112, Rarity.RARE, mage.cards.t.TitanHunter.class)); + cards.add(new SetCardInfo("Tranquil Cove", 329, Rarity.COMMON, mage.cards.t.TranquilCove.class)); + cards.add(new SetCardInfo("Tranquil Expanse", 330, Rarity.UNCOMMON, mage.cards.t.TranquilExpanse.class)); + cards.add(new SetCardInfo("Trostani Discordant", 253, Rarity.MYTHIC, mage.cards.t.TrostaniDiscordant.class)); + cards.add(new SetCardInfo("True Conviction", 38, Rarity.RARE, mage.cards.t.TrueConviction.class)); + cards.add(new SetCardInfo("Tyrant's Familiar", 165, Rarity.RARE, mage.cards.t.TyrantsFamiliar.class)); + cards.add(new SetCardInfo("Unbreathing Horde", 113, Rarity.RARE, mage.cards.u.UnbreathingHorde.class)); + cards.add(new SetCardInfo("Undead Augur", 114, Rarity.UNCOMMON, mage.cards.u.UndeadAugur.class)); + cards.add(new SetCardInfo("Undermine", 254, Rarity.RARE, mage.cards.u.Undermine.class)); + cards.add(new SetCardInfo("Unleash Fury", 166, Rarity.UNCOMMON, mage.cards.u.UnleashFury.class)); + cards.add(new SetCardInfo("Unlicensed Disintegration", 255, Rarity.UNCOMMON, mage.cards.u.UnlicensedDisintegration.class)); + cards.add(new SetCardInfo("Unstable Obelisk", 286, Rarity.UNCOMMON, mage.cards.u.UnstableObelisk.class)); + cards.add(new SetCardInfo("Urborg Volcano", 331, Rarity.UNCOMMON, mage.cards.u.UrborgVolcano.class)); + cards.add(new SetCardInfo("Valor in Akros", 39, Rarity.UNCOMMON, mage.cards.v.ValorInAkros.class)); + cards.add(new SetCardInfo("Vampire Nighthawk", 115, Rarity.UNCOMMON, mage.cards.v.VampireNighthawk.class)); + cards.add(new SetCardInfo("Vampiric Rites", 116, Rarity.UNCOMMON, mage.cards.v.VampiricRites.class)); + cards.add(new SetCardInfo("Vandalblast", 167, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class)); + cards.add(new SetCardInfo("Vela the Night-Clad", 256, Rarity.MYTHIC, mage.cards.v.VelaTheNightClad.class)); + cards.add(new SetCardInfo("Vengeful Dead", 117, Rarity.COMMON, mage.cards.v.VengefulDead.class)); + cards.add(new SetCardInfo("Verdant Force", 214, Rarity.RARE, mage.cards.v.VerdantForce.class)); + cards.add(new SetCardInfo("Verix Bladewing", 168, Rarity.MYTHIC, mage.cards.v.VerixBladewing.class)); + cards.add(new SetCardInfo("Victimize", 118, Rarity.UNCOMMON, mage.cards.v.Victimize.class)); + cards.add(new SetCardInfo("Vitu-Ghazi, the City-Tree", 332, Rarity.UNCOMMON, mage.cards.v.VituGhaziTheCityTree.class)); + cards.add(new SetCardInfo("Vizier of the Scorpion", 119, Rarity.UNCOMMON, mage.cards.v.VizierOfTheScorpion.class)); + cards.add(new SetCardInfo("Voice of Many", 215, Rarity.UNCOMMON, mage.cards.v.VoiceOfMany.class)); + cards.add(new SetCardInfo("Vow of Duty", 40, Rarity.UNCOMMON, mage.cards.v.VowOfDuty.class)); + cards.add(new SetCardInfo("Warden of Evos Isle", 63, Rarity.UNCOMMON, mage.cards.w.WardenOfEvosIsle.class)); + cards.add(new SetCardInfo("Wayfarer's Bauble", 287, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); + cards.add(new SetCardInfo("White Sun's Zenith", 41, Rarity.RARE, mage.cards.w.WhiteSunsZenith.class)); + cards.add(new SetCardInfo("Wildfire Devils", 170, Rarity.RARE, mage.cards.w.WildfireDevils.class)); + cards.add(new SetCardInfo("Wild Ricochet", 169, Rarity.RARE, mage.cards.w.WildRicochet.class)); + cards.add(new SetCardInfo("Windreader Sphinx", 64, Rarity.RARE, mage.cards.w.WindreaderSphinx.class)); + cards.add(new SetCardInfo("Winged Words", 65, Rarity.COMMON, mage.cards.w.WingedWords.class)); + cards.add(new SetCardInfo("Withered Wretch", 120, Rarity.UNCOMMON, mage.cards.w.WitheredWretch.class)); + cards.add(new SetCardInfo("Worn Powerstone", 288, Rarity.UNCOMMON, mage.cards.w.WornPowerstone.class)); + cards.add(new SetCardInfo("Zombie Apocalypse", 121, Rarity.RARE, mage.cards.z.ZombieApocalypse.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java index f1bacd4604f..dada6568c0a 100644 --- a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java +++ b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java @@ -50,6 +50,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet { cards.add(new SetCardInfo("Brushland", 297, Rarity.RARE, mage.cards.b.Brushland.class)); cards.add(new SetCardInfo("Cabal Coffers", 360, Rarity.MYTHIC, mage.cards.c.CabalCoffers.class)); cards.add(new SetCardInfo("Call Forth the Tempest", 509, Rarity.RARE, mage.cards.c.CallForthTheTempest.class)); + cards.add(new SetCardInfo("Call for Aid", 30, Rarity.RARE, mage.cards.c.CallForAid.class)); cards.add(new SetCardInfo("Call for Unity", 163, Rarity.RARE, mage.cards.c.CallForUnity.class)); cards.add(new SetCardInfo("Canopy Vista", 298, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Castle Ardenvale", 361, Rarity.MYTHIC, mage.cards.c.CastleArdenvale.class)); diff --git a/Mage.Sets/src/mage/sets/TheBigScore.java b/Mage.Sets/src/mage/sets/TheBigScore.java new file mode 100644 index 00000000000..83d9b87a3a9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/TheBigScore.java @@ -0,0 +1,23 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class TheBigScore extends ExpansionSet { + + private static final TheBigScore instance = new TheBigScore(); + + public static TheBigScore getInstance() { + return instance; + } + + private TheBigScore() { + super("The Big Score", "BIG", ExpansionSet.buildDate(2024, 4, 19), SetType.SUPPLEMENTAL_STANDARD_LEGAL); + this.blockName = "Outlaws of Thunder Junction"; + this.hasBasicLands = false; + this.hasBoosters = false; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java index 54167fcd163..342b3c92df0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java @@ -22,8 +22,8 @@ public class BloodMoonTest extends CardTestPlayerBase { // which replacement effects apply and how they apply, check the characteristics of the permanent as it // would exist on the battlefield, taking into account replacement effects that have already modified how // it enters the battlefield (see rule 616.1), continuous effects generated by the resolution of spells - // or abilities that changed the permanent’s characteristics on the stack (see rule 400.7a), and continuous - // effects from the permanent’s own static abilities, but ignoring continuous effects from any other source + // or abilities that changed the permanent's characteristics on the stack (see rule 400.7a), and continuous + // effects from the permanent's own static abilities, but ignoring continuous effects from any other source // that would affect it. // Grassland has to enter the battlefield tapped, because // the Blood Moon does not prevent ETB Replacement Effects diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java new file mode 100644 index 00000000000..5befc0cfd8e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java @@ -0,0 +1,176 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.TurnFaceUpAbility; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import mage.view.CardView; +import mage.view.GameView; +import mage.view.PermanentView; +import mage.view.PlayerView; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.List; + +/** + * Most of the face down logic was tested in MorphTest, here are tests for disguise related only + * + * @author JayDi85 + */ +public class DisguiseTest extends CardTestPlayerBase { + + @Test + public void test_NormalPlay_ClientData_CostRulesVisible() { + // it checks rules visible for face down cards, main logic: + // - real face up abilities uses special cost; + // - it must be hidden from opponent + // - so it must be replaced in rules by non-cost versions (e.g. text only) + + String FACE_DOWN_SPELL = "with no text, no name, no subtypes"; + String FACE_DOWN_TRIGGER = "When "; + String FACE_DOWN_FACE_UP = "down permanent face up"; + + + // {R}{W} + // Disguise {R/W}{R/W} (You may cast this card face down for {3} as a 2/2 creature with ward {2}. + // Turn it face up any time for its disguise cost.) + // When Dog Walker is turned face up, create two tapped 1/1 white Dog creature tokens. + // Ward {2}. (Whenever it becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.) + addCard(Zone.HAND, playerA, "Dog Walker@dog"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2); + // + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + + + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker", 0); + + // prepare face down + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker using Disguise"); + runCode("face up on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Assert.assertEquals("stack, server - can't find spell", 1, currentGame.getStack().size()); + SpellAbility spellAbility = (SpellAbility) currentGame.getStack().getFirst().getStackAbility(); + Assert.assertEquals("stack, server - can't find spell", "Cast Dog Walker using Disguise", spellAbility.getName()); + CardView spellView = getGameView(playerA).getStack().values().stream().findFirst().orElse(null); + Assert.assertNotNull("stack, client: can't find spell", spellView); + + // make sure rules visible + assertRuleExist("client side, stack: face down spell - show", spellView.getRules(), FACE_DOWN_SPELL, true); + assertRuleExist("client side, stack: face up - hide", spellView.getRules(), FACE_DOWN_FACE_UP, false); + assertRuleExist("client side, stack: triggered ability - hide", spellView.getRules(), FACE_DOWN_TRIGGER, false); + }); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker", 0); + checkPermanentCount("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + runCode("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + // server side + Permanent permanent = currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(Permanent::isDisguised) + .findFirst() + .orElse(null); + Assert.assertNotNull("server side: can't find disguised permanent", permanent); + Assert.assertEquals("server side: wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), permanent.getName()); + Assert.assertEquals("server side: wrong color", "", permanent.getColor(currentGame).toString()); + Assert.assertEquals("server side: wrong power", "2", permanent.getPower().toString()); + Assert.assertEquals("server side: wrong toughness", "2", permanent.getToughness().toString()); + + // make sure real abilities exists + // trigger + Ability ability = permanent.getAbilities(currentGame).stream().filter(a -> a instanceof TurnedFaceUpSourceTriggeredAbility).findFirst().orElse(null); + Assert.assertNotNull("server side: must have face up triggered ability", ability); + Assert.assertFalse("server side: face up triggered ability must be hidden", ability.getRuleVisible()); + // face up + ability = permanent.getAbilities(currentGame).stream().filter(a -> a instanceof TurnFaceUpAbility).findFirst().orElse(null); + Assert.assertNotNull("server side: must have turn face up ability", ability); + String foundRule = permanent.getRules(currentGame).stream().filter(r -> r.contains("{R/W}")).findFirst().orElse(null); + // failed here? search BecomesFaceDownCreatureEffect and additionalAbilities + Assert.assertNull("server side: turn face up ability with {R/W} cost must be replaced by text only without cost", foundRule); + + // client side - controller + GameView gameView = getGameView(playerA); + PermanentView permanentView = gameView.getMyPlayer().getBattlefield().values() + .stream() + .filter(PermanentView::isDisguised) + .findFirst() + .orElse(null); + Assert.assertNotNull("client side - controller: can't find disguised permanent", permanentView); + Assert.assertEquals("client side - controller: wrong name", "Disguise: Dog Walker", permanentView.getName()); + Assert.assertEquals("client side - controller: wrong color", "", permanentView.getColor().toString()); + Assert.assertEquals("client side - controller: wrong power", "2", permanentView.getPower()); + Assert.assertEquals("client side - controller: wrong toughness", "2", permanentView.getToughness()); + // make sure rules visible + assertRuleExist("client side, controller: face down spell - show", permanentView.getRules(), FACE_DOWN_SPELL, true); + assertRuleExist("client side, controller: face up - hide", permanentView.getRules(), FACE_DOWN_FACE_UP, false); + assertRuleExist("client side, controller: triggered ability - hide", permanentView.getRules(), FACE_DOWN_TRIGGER, false); + assertRuleExist("client side, controller: {R/W} cost hide", permanentView.getRules(), "{R/W}", false); + + // client side - opponent + gameView = getGameView(playerB); + PlayerView playerView = gameView.getPlayers() + .stream() + .filter(p -> p.getName().equals(playerA.getName())) + .findFirst() + .orElse(null); + Assert.assertNotNull(playerView); + permanentView = playerView.getBattlefield().values() + .stream() + .filter(PermanentView::isDisguised) + .findFirst() + .orElse(null); + Assert.assertNotNull("client side - opponent: can't find disguised permanent", permanentView); + Assert.assertEquals("client side - opponent: wrong name", "Disguise", permanentView.getName()); + Assert.assertEquals("client side - opponent: wrong color", "", permanentView.getColor().toString()); + Assert.assertEquals("client side - opponent: wrong power", "2", permanentView.getPower()); + Assert.assertEquals("client side - opponent: wrong toughness", "2", permanentView.getToughness()); + // make sure rules visible + assertRuleExist("client side, opponent: face down spell - show", permanentView.getRules(), FACE_DOWN_SPELL, true); + assertRuleExist("client side, opponent: face up - hide", permanentView.getRules(), FACE_DOWN_FACE_UP, false); + assertRuleExist("client side, opponent: triggered ability - hide", permanentView.getRules(), FACE_DOWN_TRIGGER, false); + assertRuleExist("client side, opponent: {R/W} cost hide", permanentView.getRules(), "{R/W}", false); + }); + + // make sure ward works too + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt"); + addTarget(playerB, "@dog"); // target face down card by alias + setChoice(playerB, true); // try ward pay but no mana, so bolt will be fizzled + + // do face up and generate tokens + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R/W}{R/W}: Turn"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dog Walker", 1); + checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dog Token", 2); + runCode("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + Permanent permanent = currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(p -> p.getName().equals("Dog Walker")) + .findFirst() + .orElse(null); + Assert.assertNotNull("server side: can't find normal permanent", permanent); + Assert.assertEquals("server side: wrong name", "Dog Walker", permanent.getName()); + Assert.assertEquals("server side: wrong color", "WR", permanent.getColor(currentGame).toString()); + Assert.assertEquals("server side: wrong power", "3", permanent.getPower().toString()); + Assert.assertEquals("server side: wrong toughness", "1", permanent.getToughness().toString()); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + private void assertRuleExist(String info, List rules, String searchPart, boolean mustExists) { + String foundAbility = rules.stream().filter(r -> r.contains(searchPart)).findFirst().orElse(null); + if (mustExists) { + Assert.assertTrue(info, foundAbility != null); + } else { + Assert.assertFalse(info, foundAbility != null); + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java index 14bef916591..39bee4954fc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java @@ -1,11 +1,15 @@ package org.mage.test.cards.abilities.keywords; +import mage.MageObject; import mage.cards.Card; +import mage.cards.repository.TokenRepository; import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.util.CardUtil; import mage.view.CardView; import mage.view.GameView; import mage.view.PermanentView; @@ -20,6 +24,197 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class ManifestTest extends CardTestPlayerBase { + @Test + public void test_Simple_ManifestFromOwnLibrary() { + // Manifest the top card of your library. + addCard(Zone.HAND, playerA, "Soul Summons", 1); // {1}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // manifest + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + } + + @Test + public void test_Simple_ManifestFromHand() { + // {T}: Manifest a card from your hand. + addCard(Zone.BATTLEFIELD, playerA, "Scroll of Fate", 1); + addCard(Zone.HAND, playerA, "Basking Rootwalla", 1); // 1/1 + + // manifest + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Manifest"); + addTarget(playerA, "Basking Rootwalla"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + } + + @Test + public void test_Simple_ManifestTargetPlayer() { + // Exile target creature. Its controller manifests the top card of their library. + addCard(Zone.HAND, playerA, "Reality Shift", 1); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); + + // manifest + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reality Shift"); + addTarget(playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + } + + @Test + public void test_Simple_ManifestFromOpponentLibrary() { + // At the beginning of each opponent's upkeep, you manifest the top card of that player's library. + addCard(Zone.BATTLEFIELD, playerA, "Thieving Amalgam", 1); + + // no drew on first turn, so use 5 turns to check same libs size at the end + runCode("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Assert.assertEquals("libraries must be same on start", playerA.getLibrary().size(), playerB.getLibrary().size()); + }); + + // turn 1 + checkPermanentCount("turn 1.A - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 1.B - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + // turn 2 + checkPermanentCount("turn 2.A - +1 face down", 2, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + checkPermanentCount("turn 2.B - no face down", 2, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + // turn 3 + checkPermanentCount("turn 3.A - +1 face down", 3, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + checkPermanentCount("turn 3.B - no face down", 3, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + // turn 4 + checkPermanentCount("turn 4.A - +2 face down", 4, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + checkPermanentCount("turn 4.B - no face down", 4, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + // turn 5 + checkPermanentCount("turn 5.A - +2 face down", 5, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + checkPermanentCount("turn 5.B - no face down", 5, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + Assert.assertEquals("manifested cards must be taken from opponent's library", 2, playerA.getLibrary().size() - playerB.getLibrary().size()); + } + + private void runManifestThenBlink(String cardToManifest, String cardAfterBlink) { + // split, mdfc and other cards must be able to manifested + // bug: https://github.com/magefree/mage/issues/10608 + skipInitShuffling(); + + // Manifest the top card of your library. + addCard(Zone.HAND, playerA, "Soul Summons", 1); // {1}{W}, sorcery + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // + // Exile target creature you control, then return that card to the battlefield under your control. + addCard(Zone.HAND, playerA, "Cloudshift", 1); // {W}, instant + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // + addCard(Zone.LIBRARY, playerA, cardToManifest, 1); + + // manifest + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("need face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + + // blink + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString()); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("need no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + runCode("after blink", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + if (cardAfterBlink == null) { + Assert.assertEquals("after blink card must keep in exile", + 1, currentGame.getExile().getAllCardsByRange(currentGame, playerA.getId()).size()); + } else { + String realPermanentName = currentGame.getBattlefield().getAllPermanents() + .stream() + .map(MageObject::getName) + .filter(name -> name.equals(cardAfterBlink)) + .findFirst() + .orElse(null); + Assert.assertEquals("after blink card must go to battlefield", + cardAfterBlink, realPermanentName); + } + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_ManifestThenBlink_Creature() { + runManifestThenBlink("Grizzly Bears", "Grizzly Bears"); + } + + @Test + public void test_ManifestThenBlink_Instant() { + runManifestThenBlink("Lightning Bolt", null); + } + + @Test + public void test_ManifestThenBlink_MDFC_Creature() { + runManifestThenBlink("Akoum Warrior // Akoum Teeth", "Akoum Warrior"); + } + + @Test + public void test_ManifestThenBlink_MDFC_LandOnMainSide() { + runManifestThenBlink("Barkchannel Pathway // Tidechannel Pathway", "Barkchannel Pathway"); + } + + @Test + public void test_ManifestThenBlink_MDFC_LandOnSecondSide() { + runManifestThenBlink("Bala Ged Recovery // Bala Ged Sanctuary", null); + } + + @Test + public void test_ManifestThenBlink_Split_Normal() { + runManifestThenBlink("Assault // Battery", null); + } + + @Test + public void test_ManifestThenBlink_Split_Fused() { + runManifestThenBlink("Alive // Well", null); + } + + @Test + public void test_ManifestThenBlink_Split_Aftermath() { + runManifestThenBlink("Dusk // Dawn", null); + } + + @Test + public void test_ManifestThenBlink_Meld() { + runManifestThenBlink("Graf Rats", "Graf Rats"); + } + + @Test + public void test_ManifestThenBlink_Adventure() { + runManifestThenBlink("Ardenvale Tactician // Dizzying Swoop", "Ardenvale Tactician"); + } + /** * Tests that ETB triggered abilities did not trigger for manifested cards */ @@ -535,9 +730,16 @@ public class ManifestTest extends CardTestPlayerBase { Permanent perm = game.getBattlefield().getAllPermanents() .stream() .filter(permanent -> permanent.isFaceDown(game)) + .filter(permanent -> { + Assert.assertEquals("face down permanent must have not name", "", permanent.getName()); + // TODO: buggy, manifested card must have some rules + //Assert.assertTrue("face down permanent must have abilities", permanent.getAbilities().size() > 0); + return true; + }) .findFirst() .orElse(null); Assert.assertNotNull(perm); + Assert.assertEquals("server side face down permanent must have empty name", EmptyNames.FACE_DOWN_CREATURE.toString(), perm.getName()); GameView gameView = new GameView(game.getState(), game, viewFromPlayer.getId(), null); PlayerView playerView = gameView.getPlayers() .stream() @@ -548,29 +750,36 @@ public class ManifestTest extends CardTestPlayerBase { PermanentView permanentView = playerView.getBattlefield().values() .stream() .filter(CardView::isFaceDown) + .filter(p -> { + CardView debugView = new CardView((PermanentCard) currentGame.getPermanent(p.getId()), currentGame, false, false); + Assert.assertNotEquals("face down view must have name", "", p.getName()); + // TODO: buggy, manifested card must have some rules + //Assert.assertTrue("face down view must have abilities", p.getRules().size() > 0); + return true; + }) .findFirst() .orElse(null); Assert.assertNotNull(permanentView); return permanentView; } - private void assertFaceDown(String info, PermanentView faceDownPermanent, String realPermanentName, boolean realInfoMustBeVisible) { - if (realInfoMustBeVisible) { - // show all info - Assert.assertEquals(realPermanentName, faceDownPermanent.getName()); // show real name - Assert.assertEquals("2", faceDownPermanent.getPower()); - Assert.assertEquals("2", faceDownPermanent.getToughness()); - // - Assert.assertNotNull(faceDownPermanent.getOriginal()); - Assert.assertEquals(realPermanentName, faceDownPermanent.getOriginal().getName()); - } else { - // hide original info - Assert.assertEquals(info, "", faceDownPermanent.getName()); - Assert.assertEquals(info, "2", faceDownPermanent.getPower()); - Assert.assertEquals(info, "2", faceDownPermanent.getToughness()); - Assert.assertNull(info, faceDownPermanent.getOriginal()); - } + private void assertFaceDownManifest(String checkInfo, PermanentView faceDownPermanentView, String needRealName, boolean needShowRealInfo) { + String info = checkInfo + " - " + faceDownPermanentView; + String needName = CardUtil.getCardNameForGUI(needShowRealInfo ? needRealName : "", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST); + // check view + Assert.assertTrue(info + " - wrong face down status", faceDownPermanentView.isFaceDown()); + Assert.assertEquals(info + " - wrong name", needName, faceDownPermanentView.getName()); // show real name + Assert.assertEquals(info + " - wrong power", "2", faceDownPermanentView.getPower()); + Assert.assertEquals(info + " - wrong toughness", "2", faceDownPermanentView.getToughness()); + + // check original info + if (needShowRealInfo) { + Assert.assertNotNull(info + " - miss original card data", faceDownPermanentView.getOriginal()); + Assert.assertEquals(info + " - wrong original card name", needRealName, faceDownPermanentView.getOriginal().getName()); + } else { + Assert.assertNull(info + " - original data must be hidden", faceDownPermanentView.getOriginal()); + } } @Test @@ -587,27 +796,58 @@ public class ManifestTest extends CardTestPlayerBase { runCode("on active game", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { // hide from opponent PermanentView permanent = findFaceDownPermanent(game, playerA, playerB); - assertFaceDown("in game: must hide from opponent", permanent, "Mountain", false); + assertFaceDownManifest("in game: must hide from opponent", permanent, "Mountain", false); // show for yourself permanent = findFaceDownPermanent(game, playerB, playerB); - assertFaceDown("in game: must show for yourself", permanent, "Mountain", true); + assertFaceDownManifest("in game: must show for yourself", permanent, "Mountain", true); }); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // workaround to force end game (can't use other test commands after that) playerA.won(currentGame); Assert.assertTrue(currentGame.hasEnded()); // show all after game end PermanentView permanent = findFaceDownPermanent(currentGame, playerA, playerB); - assertFaceDown("end game: must show for opponent", permanent, "Mountain", true); + assertFaceDownManifest("end game: must show for opponent", permanent, "Mountain", true); // permanent = findFaceDownPermanent(currentGame, playerB, playerB); - assertFaceDown("end game: must show for yourself", permanent, "Mountain", true); + assertFaceDownManifest("end game: must show for yourself", permanent, "Mountain", true); } + + @Test + public void testJeskaiInfiltrator() { + // Whenever Jeskai Infiltrator deals combat damage to a player, + // exile it and the top card of your library in a face-down pile, shuffle that pile, then manifest those cards. + String infiltrator = "Jeskai Infiltrator"; // 2/3 + String excommunicate = "Excommunicate"; // 2W Sorcery, Put target creature on top of its owner's library + String missionary = "Lone Missionary"; // 2/1 for 1W, ETB gain 4 life + + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + addCard(Zone.BATTLEFIELD, playerA, infiltrator); + addCard(Zone.BATTLEFIELD, playerA, missionary); + addCard(Zone.HAND, playerA, excommunicate); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, excommunicate, missionary); + + attack(1, playerA, infiltrator, playerB); + + checkPlayableAbility("missionary manifest",1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}{W}: Turn ", true); + checkPlayableAbility("infiltrator manifest",1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{U}: Turn ", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, excommunicate, 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertLife(playerA, 20); + assertLife(playerB, 18); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 752ec7844cf..2c2837c3043 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -4,16 +4,21 @@ import mage.cards.Card; import mage.constants.*; import mage.filter.Filter; import mage.game.permanent.Permanent; +import mage.view.GameView; +import mage.view.PermanentView; +import mage.view.PlayerView; import org.junit.Assert; import org.junit.Test; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * @author levelX2 + * @author levelX2, JayDi85 */ public class MorphTest extends CardTestPlayerBase { + // DisguiseTest contains additional rules generation tests for face down + /** * Tests if a creature with Morph is cast normal, it behaves as normal * creature @@ -521,14 +526,10 @@ public class MorphTest extends CardTestPlayerBase { setStrictChooseMode(true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury using Morph"); -// showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); -// showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form"); addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); -// showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA); -// showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -1103,10 +1104,17 @@ public class MorphTest extends CardTestPlayerBase { @Test public void test_MorphIsColorlessFlash() { + // creature + // Morph {4}{G} addCard(Zone.HAND, playerA, "Pine Walker", 1); + // land + // Morph {2} addCard(Zone.HAND, playerA, "Zoetic Cavern", 1); - addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // + // You may cast colorless spells and artifact spells as though they had flash. + addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Pine Walker using Morph"); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Zoetic Cavern using Morph"); @@ -1221,4 +1229,69 @@ public class MorphTest extends CardTestPlayerBase { assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); } + private void assertMorphedFaceDownColor(String info, String needColor) { + Permanent permanent = currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(Permanent::isMorphed) + .findFirst() + .orElse(null); + Assert.assertNotNull(info + ", server side: can't find morphed permanent", permanent); + Assert.assertEquals(info + ", server side: wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), permanent.getName()); + Assert.assertEquals(info + ", server side: wrong color", needColor, permanent.getColor(currentGame).toString()); + + // client side - controller + GameView gameView = getGameView(playerA); + PermanentView permanentView = gameView.getMyPlayer().getBattlefield().values() + .stream() + .filter(PermanentView::isMorphed) + .findFirst() + .orElse(null); + Assert.assertNotNull(info + ", client side - controller: can't find morphed permanent", permanentView); + Assert.assertEquals(info + ", client side - controller: wrong name", "Morph: Zoetic Cavern", permanentView.getName()); + Assert.assertEquals(info + ", client side - controller: wrong color", needColor, permanentView.getColor().toString()); + + // client side - opponent + gameView = getGameView(playerB); + PlayerView playerView = gameView.getPlayers().stream().filter(p -> p.getName().equals(playerA.getName())).findFirst().orElse(null); + Assert.assertNotNull(playerView); + permanentView = playerView.getBattlefield().values() + .stream() + .filter(PermanentView::isMorphed) + .findFirst() + .orElse(null); + Assert.assertNotNull(info + ", client side - opponent: can't find morphed permanent", permanentView); + Assert.assertEquals(info + ", client side - opponent: wrong name", "Morph", permanentView.getName()); + Assert.assertEquals(info + ", client side - opponent: wrong color", needColor, permanentView.getColor().toString()); + } + + @Test + public void test_Morph_MustGetColor() { + // Morph {2} + addCard(Zone.HAND, playerA, "Zoetic Cavern"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + // As Painter's Servant enters the battlefield, choose a color. + // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. + addCard(Zone.HAND, playerA, "Painter's Servant"); // {2} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // prepare face down + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + runCode("face down before color", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + assertMorphedFaceDownColor(info, ""); + }); + + // add effect with new green color for a face down + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); + setChoice(playerA, "Green"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + runCode("face down with G color", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + assertMorphedFaceDownColor(info, "G"); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java index 3dc234980bd..aec12935658 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java @@ -23,9 +23,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class PrototypeTest extends CardTestPlayerBase { + // Prototype {2}{R} - 3/2 private static final String automaton = "Blitz Automaton"; private static final String withPrototype = " using Prototype"; private static final String automatonWithPrototype = automaton+withPrototype; + private static final String bolt = "Lightning Bolt"; private static final String cloudshift = "Cloudshift"; private static final String clone = "Clone"; @@ -89,6 +91,7 @@ public class PrototypeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.HAND, playerA, automaton); + showAvailableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java index 5a8536f4b70..7cd178aa7e6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java @@ -29,6 +29,9 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Epochrasite"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Epochrasite"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(7, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -55,6 +58,9 @@ public class SuspendTest extends CardTestPlayerBase { activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Exile a nonland card from your hand: Put four time counters on the exiled card. If it doesn't have suspend, it gains suspend"); setChoice(playerA, "Silvercoat Lion"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(11, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -87,6 +93,9 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Delay", "Silvercoat Lion"); + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); setStopAt(7, PhaseStep.BEGIN_COMBAT); execute(); @@ -109,6 +118,7 @@ public class SuspendTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -128,6 +138,7 @@ public class SuspendTest extends CardTestPlayerBase { checkPlayableAbility("Can't cast directly", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ancestral", false); // castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Vision", playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -150,6 +161,7 @@ public class SuspendTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Suppression Field", 1); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -183,8 +195,10 @@ public class SuspendTest extends CardTestPlayerBase { castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Knowledge Pool"); + setChoice(playerA, true); // choose yes to cast addTarget(playerA, playerB); + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); @@ -226,6 +240,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again + setChoice(playerA, true); // choose yes to cast addTarget(playerA, playerB); checkLife("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 3); checkGraveyardCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); @@ -259,6 +274,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wear // Tear", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again + setChoice(playerA, true); // choose yes to cast addTarget(playerA, "Bident of Thassa"); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bident of Thassa", 0); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bow of Nylea", 1); @@ -300,6 +316,7 @@ public class SuspendTest extends CardTestPlayerBase { checkExileCount("after counter", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Wear // Tear", 1); // 3 time counters removes on upkeep (3, 5, 7) and cast again (fused cards can't be played from exile zone, so select split spell only) + setChoice(playerA, true); // choose yes to cast setChoice(playerA, "Cast Wear"); addTarget(playerA, "Bident of Thassa"); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bident of Thassa", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayFromNonHandZoneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayFromNonHandZoneTest.java index 73d4f9c358e..a8b11e5ea66 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayFromNonHandZoneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayFromNonHandZoneTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.asthough; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; @@ -20,12 +21,13 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBaseWithAIHelps { addCard(Zone.HAND, playerA, "Worldheart Phoenix"); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Worldheart Phoenix"); // can only be cast by {W}{U}{B}{R}{G} + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Worldheart Phoenix"); setStopAt(1, PhaseStep.END_COMBAT); execute(); assertPowerToughness(playerA, "Worldheart Phoenix", 2, 2); + assertCounterCount(playerA, "Worldheart Phoenix", CounterType.P1P1, 0); } @Test @@ -73,6 +75,7 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBaseWithAIHelps { execute(); assertPermanentCount(playerA, "Worldheart Phoenix", 1); + assertCounterCount(playerA, "Worldheart Phoenix", CounterType.P1P1, 2); } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index 1dfa057c598..d8b9b9886b0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -649,8 +649,8 @@ public class CopySpellTest extends CardTestPlayerBase { @Test public void test_SimpleCopy_Card() { - Card sourceCard = CardRepository.instance.findCard("Grizzly Bears").getCard(); - Card originalCard = CardRepository.instance.findCard("Grizzly Bears").getCard(); + Card sourceCard = CardRepository.instance.findCard("Grizzly Bears").createCard(); + Card originalCard = CardRepository.instance.findCard("Grizzly Bears").createCard(); prepareZoneAndZCC(originalCard); Card copiedCard = currentGame.copyCard(originalCard, null, playerA.getId()); // main @@ -664,8 +664,8 @@ public class CopySpellTest extends CardTestPlayerBase { @Test public void test_SimpleCopy_SplitCard() { - SplitCard sourceCard = (SplitCard) CardRepository.instance.findCard("Alive // Well").getCard(); - SplitCard originalCard = (SplitCard) CardRepository.instance.findCard("Alive // Well").getCard(); + SplitCard sourceCard = (SplitCard) CardRepository.instance.findCard("Alive // Well").createCard(); + SplitCard originalCard = (SplitCard) CardRepository.instance.findCard("Alive // Well").createCard(); prepareZoneAndZCC(originalCard); SplitCard copiedCard = (SplitCard) currentGame.copyCard(originalCard, null, playerA.getId()); // main @@ -693,8 +693,8 @@ public class CopySpellTest extends CardTestPlayerBase { @Test public void test_SimpleCopy_AdventureCard() { - AdventureCard sourceCard = (AdventureCard) CardRepository.instance.findCard("Animating Faerie").getCard(); - AdventureCard originalCard = (AdventureCard) CardRepository.instance.findCard("Animating Faerie").getCard(); + AdventureCard sourceCard = (AdventureCard) CardRepository.instance.findCard("Animating Faerie").createCard(); + AdventureCard originalCard = (AdventureCard) CardRepository.instance.findCard("Animating Faerie").createCard(); prepareZoneAndZCC(originalCard); AdventureCard copiedCard = (AdventureCard) currentGame.copyCard(originalCard, null, playerA.getId()); // main @@ -715,8 +715,8 @@ public class CopySpellTest extends CardTestPlayerBase { @Test public void test_SimpleCopy_MDFC() { - ModalDoubleFacedCard sourceCard = (ModalDoubleFacedCard) CardRepository.instance.findCard("Agadeem's Awakening").getCard(); - ModalDoubleFacedCard originalCard = (ModalDoubleFacedCard) CardRepository.instance.findCard("Agadeem's Awakening").getCard(); + ModalDoubleFacedCard sourceCard = (ModalDoubleFacedCard) CardRepository.instance.findCard("Agadeem's Awakening").createCard(); + ModalDoubleFacedCard originalCard = (ModalDoubleFacedCard) CardRepository.instance.findCard("Agadeem's Awakening").createCard(); prepareZoneAndZCC(originalCard); ModalDoubleFacedCard copiedCard = (ModalDoubleFacedCard) currentGame.copyCard(originalCard, null, playerA.getId()); // main @@ -889,9 +889,10 @@ public class CopySpellTest extends CardTestPlayerBase { } private void prepareZoneAndZCC(Card originalCard) { - // prepare custom zcc and zone for copy testing + // prepare custom zcc and zone for copy testing (it's not real game) + // HAND zone is safest way (some card types require diff zones for stack/battlefield, e.g. MDFC) originalCard.setZoneChangeCounter(5, currentGame); - originalCard.setZone(Zone.STACK, currentGame); + originalCard.setZone(Zone.HAND, currentGame); } private void cardsMustHaveSameZoneAndZCC(Card originalCard, Card copiedCard, String infoPrefix) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java index f6d7f704213..1eb571247d4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java @@ -999,4 +999,33 @@ public class ModalDoubleFacedCardsTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.END_TURN); execute(); } + + @Test + public void test_Battlefield_MustHaveAbilitiesFromOneSideOnly() { + // possible bug: test framework adds second side abilities + + // left side - Reidane, God of the Worthy: + // Snow lands your opponents control enter the battlefield tapped. + // right side - Valkmira, Protector's Shield: + // If a source an opponent controls would deal damage to you or a permanent you control, prevent 1 of that damage. + // Whenever you or another permanent you control becomes the target of a spell or ability an opponent controls, + // counter that spell or ability unless its controller pays {1}. + addCard(Zone.BATTLEFIELD, playerB, "Reidane, God of the Worthy", 1); + // + addCard(Zone.HAND, playerA, "Snow-Covered Forest", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + // cast, second side effects must be ignored (e.g. counter trigger) + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snow-Covered Forest"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + addTarget(playerA, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTappedCount("Snow-Covered Forest", true, 1); + assertLife(playerB, 20 - 3); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java index 27244a31ea3..b895bf7b1f0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeLandTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.cost.sacrifice; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.util.RandomUtil; import org.junit.Test; import org.mage.test.sba.PlaneswalkerRuleTest; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -29,9 +30,8 @@ public class SacrificeLandTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, soldeviExcavations); addCard(Zone.BATTLEFIELD, playerA, "Island"); - Random random = new Random(); - boolean sacFirstLand = random.nextBoolean(); - boolean sacSecondLand = random.nextBoolean(); + boolean sacFirstLand = RandomUtil.nextBoolean(); + boolean sacSecondLand = RandomUtil.nextBoolean(); playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations); setChoice(playerA, sacFirstLand); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java index 20fbeb98ebc..924d6f13381 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java @@ -18,7 +18,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { // Flying, lifelink // Pay 7 life: Draw seven cards. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Griselbrand", true).getMockCard() + CardRepository.instance.findCard("Griselbrand", true).createMockCard() )); setLife(playerA, 20); @@ -40,7 +40,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { // A player losing unspent mana causes that player to lose that much life. // {1}, {T}: Each player adds {B}{R}{G}. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Yurlok of Scorch Thrash", true).getMockCard() + CardRepository.instance.findCard("Yurlok of Scorch Thrash", true).createMockCard() )); setLife(playerA, 20); @@ -66,7 +66,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { public void testEmblemOfOmniscience() { // You may cast spells from your hand without paying their mana costs. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Omniscience", true).getMockCard() + CardRepository.instance.findCard("Omniscience", true).createMockCard() )); // Colossal Dreadmaw {4}{G}{G} @@ -85,7 +85,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { public void testEmblemOfParadoxEngine() { // Whenever you cast a spell, untap all nonland permanents you control. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Paradox Engine", true).getMockCard() + CardRepository.instance.findCard("Paradox Engine", true).createMockCard() )); // {T}: Add {G}. @@ -136,7 +136,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { // If an effect would put one or more counters on a permanent you // control, it puts twice that many of those counters on that permanent instead. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Doubling Season", true).getMockCard() + CardRepository.instance.findCard("Doubling Season", true).createMockCard() )); // {T}: Add {W}. @@ -191,7 +191,7 @@ public class EmblemOfCardTest extends CardTestPlayerBase { // The first spell you cast each turn has cascade. addEmblem(playerA, new EmblemOfCard( - CardRepository.instance.findCard("Maelstrom Nexus", true).getMockCard() + CardRepository.instance.findCard("Maelstrom Nexus", true).createMockCard() )); // Grizzly Bears {1}{G} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/CaseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/CaseTest.java index 74bc6754b13..1b72a531a96 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/CaseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/CaseTest.java @@ -1,6 +1,8 @@ package org.mage.test.cards.enchantments; +import mage.constants.CardType; import mage.constants.PhaseStep; +import mage.constants.SubType; import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -75,6 +77,45 @@ public class CaseTest extends CardTestPlayerBase { assertHandCount(playerA, 2); } + // CardsPutIntoGraveyardWatcher was updated to work correctly with cards + // going to the graveyard from other zones than the battlefield. This test + // checks this by cycling a card from the hand. + @Test + public void test_CaseOfTheGorgonsKiss() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 + 1 + 2); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.HAND, playerA, "Walking Ballista"); + addCard(Zone.HAND, playerA, "Case of the Gorgon's Kiss"); + addCard(Zone.HAND, playerA, "Angel of the God-Pharaoh"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Walking Ballista"); + setChoice(playerA, "X=1"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Walking Ballista goes to the graveyard from the battlefield + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove", "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Case of the Gorgon's Kiss"); + // Grizzly Bears goes to the graveyard from the battlefield + addTarget(playerA, "Grizzly Bears"); // Case of the Gorgon's Kiss ETB target + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Angel of the God-Pharaoh goes to the graveyard from playerA's hand + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling"); + + checkStackObject("case is solved", 1, PhaseStep.END_TURN, playerA, "To solve", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Case of the Gorgon's Kiss", 1); + assertType("Case of the Gorgon's Kiss", CardType.CREATURE, true); + assertSubtype("Case of the Gorgon's Kiss", SubType.GORGON); + assertBasePowerToughness(playerA, "Case of the Gorgon's Kiss", 4, 4); + } + @Test public void test_CaseOfTheLockedHothouse() { addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/Plus2MaceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/Plus2MaceTest.java index 2d6711ac1d5..301af112f15 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/Plus2MaceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/Plus2MaceTest.java @@ -19,7 +19,7 @@ public class Plus2MaceTest extends CardTestPlayerBase { String cardName = "+2 Mace"; CardInfo cardinfo = CardRepository.instance.findCard(cardName); Assert.assertNotNull(cardName + " must exists", cardinfo); - Card card = cardinfo.getCard(); + Card card = cardinfo.createCard(); String cardText = GameLog.replaceNameByColoredName(card, card.getSpellAbility().toString(), null); Assert.assertTrue("card text must contain card name", cardText.contains(cardName)); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java new file mode 100644 index 00000000000..05efa5c57b2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java @@ -0,0 +1,47 @@ +package org.mage.test.cards.single.dtk; + +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class QarsiDeceiverTest extends CardTestPlayerBase { + + /** Qarsi Deceiver {1}{U} + * Creature — Naga Wizard + * {T}: Add {C}. Spend this mana only to cast a face-down creature spell, pay a mana cost to turn a + * manifested creature face up, or pay a morph cost. (A megamorph cost is a morph cost.) + */ + private static final String qd = "Qarsi Deceiver"; + private static final String mystic = "Mystic of the Hidden Way"; + /** Mystic of the Hidden Way {4}{U} + Creature — Human Monk + Mystic of the Hidden Way can’t be blocked. + Morph {2}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) + */ + + @Test + public void testCostReduction() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.HAND, playerA, qd); + addCard(Zone.HAND, playerA, mystic); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, qd); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, mystic + " using Morph"); + + checkPT("morph", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U}: Turn"); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPowerToughness(playerA, mystic, 3, 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/frf/MobRuleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/frf/MobRuleTest.java new file mode 100644 index 00000000000..8c303539877 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/frf/MobRuleTest.java @@ -0,0 +1,108 @@ +package org.mage.test.cards.single.frf; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class MobRuleTest extends CardTestPlayerBase { + + private static final String mobRule = "Mob Rule"; + /* Mob Rule {4}{R}{R} Sorcery + * Choose one — + * • Gain control of all creatures with power 4 or greater until end of turn. Untap those creatures. They gain haste until end of turn. + * • Gain control of all creatures with power 3 or less until end of turn. Untap those creatures. They gain haste until end of turn. + */ + + private static final String lord = "Merfolk Sovereign"; // 2/2 + /* Merfolk Sovereign {1}{U}{U} Creature — Merfolk Noble + * Other Merfolk creatures you control get +1/+1. + * {T}: Target Merfolk creature can’t be blocked this turn. + */ + + private static final String smog = "Smog Elemental"; // 3/3 + /* Smog Elemental {4}{B}{B} Creature — Elemental + * Flying + * Creatures with flying your opponents control get -1/-1. + */ + + private static final String bond = "Bond of Discipline"; + // {4}{W} Tap all creatures your opponents control. Creatures you control gain lifelink until end of turn. + + private static final String merfolk1 = "Merfolk of the Pearl Trident"; // 1/1 merfolk + private static final String merfolk2 = "Coral Merfolk"; // 2/1 merfolk + private static final String merfolk3 = "Coral Commando"; // 3/2 merfolk + private static final String serra = "Serra Angel"; // 4/4 flying vigilance + private static final String vizzerdrix = "Vizzerdrix"; // 6/6 + + @Test + public void testMobRuleSmall() { + addCard(Zone.HAND, playerA, mobRule); + addCard(Zone.HAND, playerA, bond); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 11); + addCard(Zone.BATTLEFIELD, playerA, smog); + addCard(Zone.BATTLEFIELD, playerB, lord); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk2); // 3/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, serra); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, vizzerdrix); // 6/6 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bond); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, mobRule); + setModeChoice(playerA, "2"); // power 3 or less + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, 2); + assertPowerToughness(playerA, smog, 3, 3); + assertPowerToughness(playerA, lord, 2, 2); + assertPowerToughness(playerA, merfolk1, 2, 2); + assertPowerToughness(playerA, merfolk2, 3, 2); + assertPowerToughness(playerB, merfolk3, 3, 2); + assertTapped(merfolk3, true); + assertPowerToughness(playerA, serra, 4, 4); + assertTapped(serra, false); + assertPowerToughness(playerB, vizzerdrix, 6, 6); + } + + + @Test + public void testMobRuleBig() { + addCard(Zone.HAND, playerA, mobRule); + addCard(Zone.HAND, playerA, bond); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 11); + addCard(Zone.BATTLEFIELD, playerA, smog); + addCard(Zone.BATTLEFIELD, playerB, lord); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk2); // 3/2 + addCard(Zone.BATTLEFIELD, playerB, merfolk3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, serra); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, vizzerdrix); // 6/6 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bond); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, mobRule); + setModeChoice(playerA, "1"); // power 4 or greater + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, 2); + assertPowerToughness(playerA, smog, 3, 3); + assertPowerToughness(playerB, lord, 2, 2); + assertPowerToughness(playerB, merfolk1, 2, 2); + assertPowerToughness(playerB, merfolk2, 3, 2); + assertPowerToughness(playerA, merfolk3, 3, 2); + assertTapped(merfolk3, false); + assertPowerToughness(playerB, serra, 3, 3); + assertPowerToughness(playerA, vizzerdrix, 6, 6); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/UnbreakableBondTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/UnbreakableBondTest.java new file mode 100644 index 00000000000..3fad968c410 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/UnbreakableBondTest.java @@ -0,0 +1,32 @@ +package org.mage.test.cards.single.iko; + +import mage.abilities.keyword.LifelinkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class UnbreakableBondTest extends CardTestPlayerBase { + + @Test + public void testLifelinkCounter() { + addCard(Zone.GRAVEYARD, playerA, "Barony Vampire"); + addCard(Zone.HAND, playerA, "Unbreakable Bond"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unbreakable Bond", "Barony Vampire"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Unbreakable Bond", 1); + assertCounterCount(playerA, "Barony Vampire", CounterType.LIFELINK, 1); + assertPowerToughness(playerA, "Barony Vampire", 3, 2); + assertAbility(playerA, "Barony Vampire", LifelinkAbility.getInstance(), true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java new file mode 100644 index 00000000000..79a6ec97e27 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java @@ -0,0 +1,63 @@ +package org.mage.test.cards.single.mkm; + +import mage.abilities.keyword.FlyingAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.k.KayaSpiritsJustice} + * @author DominionSpy + */ +public class KayaSpiritsJusticeTest extends CardTestPlayerBase { + + // Test first ability of Kaya + @Test + public void test_TriggeredAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1 + 2 + 6); + addCard(Zone.BATTLEFIELD, playerA, "Kaya, Spirits' Justice"); + addCard(Zone.BATTLEFIELD, playerA, "Llanowar Elves"); + addCard(Zone.GRAVEYARD, playerA, "Fyndhorn Elves"); + addCard(Zone.HAND, playerA, "Thraben Inspector"); + addCard(Zone.HAND, playerA, "Astrid Peth"); + addCard(Zone.HAND, playerA, "Farewell"); + + // Creates a Clue token + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Creates a Food token + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Astrid Peth"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Exile all creatures and graveyards + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Farewell"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "4"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + + // Kaya's first ability triggers twice, so choose which is put on the stack: + // Whenever one or more creatures you control and/or creature cards in your graveyard are put into exile, + // you may choose a creature card from among them. Until end of turn, target token you control becomes a copy of it, + // except it has flying. + setChoice(playerA, "Whenever", 1); + // Trigger targets + addTarget(playerA, "Clue Token"); + addTarget(playerA, "Food Token"); + // Copy choices + addTarget(playerA, "Fyndhorn Elves"); + addTarget(playerA, "Llanowar Elves"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Clue Token", 0); + assertPermanentCount(playerA, "Fyndhorn Elves", 1); + assertAbility(playerA, "Fyndhorn Elves", FlyingAbility.getInstance(), true); + assertPermanentCount(playerA, "Food Token", 0); + assertPermanentCount(playerA, "Llanowar Elves", 1); + assertAbility(playerA, "Llanowar Elves", FlyingAbility.getInstance(), true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java new file mode 100644 index 00000000000..0a54b4ded33 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java @@ -0,0 +1,112 @@ +package org.mage.test.cards.single.mkm; + +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class TenthDistrictHeroTest extends CardTestPlayerBase { + + private static final String hero = "Tenth District Hero"; + private static final String giant = "Hill Giant"; + private static final String mileva = "Mileva, the Stalwart"; + + @Test + public void testFirstAblityOnly() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, hero); + addCard(Zone.GRAVEYARD, playerA, giant); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}"); + setChoice(playerA, giant); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, hero, 4, 4); + assertSubtype(hero, SubType.HUMAN); + assertSubtype(hero, SubType.DETECTIVE); + assertAbility(playerA, hero, VigilanceAbility.getInstance(), true); + assertPermanentCount(playerA, mileva, 0); + } + + @Test + public void testSecondAblityOnly() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, hero); + addCard(Zone.GRAVEYARD, playerA, giant); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}"); + setChoice(playerA, giant); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, hero, 2, 3); + assertSubtype(hero, SubType.HUMAN); + assertNotSubtype(hero, SubType.DETECTIVE); + assertAbility(playerA, hero, VigilanceAbility.getInstance(), false); + assertPermanentCount(playerA, hero, 1); + assertPermanentCount(playerA, mileva, 0); + } + + @Test + public void testBothAbilities() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2 + 3); + addCard(Zone.BATTLEFIELD, playerA, hero); + addCard(Zone.GRAVEYARD, playerA, giant, 1 + 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}"); + setChoice(playerA, giant); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}"); + setChoice(playerA, giant); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, mileva, 5, 5); + assertSubtype(mileva, SubType.HUMAN); + assertSubtype(mileva, SubType.DETECTIVE); + assertAbility(playerA, mileva, VigilanceAbility.getInstance(), true); + assertPermanentCount(playerA, mileva, 1); + assertPermanentCount(playerA, hero, 0); + } + + @Test + public void testBothAbilitiesReversed() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2 + 3); + addCard(Zone.BATTLEFIELD, playerA, hero); + addCard(Zone.GRAVEYARD, playerA, giant, 1 + 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}"); + setChoice(playerA, giant); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}"); + setChoice(playerA, giant); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, hero, 4, 4); + assertSubtype(hero, SubType.HUMAN); + assertSubtype(hero, SubType.DETECTIVE); + assertAbility(playerA, hero, VigilanceAbility.getInstance(), true); + assertPermanentCount(playerA, mileva, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java index c63d0be9ec7..1aef884b088 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java @@ -1,9 +1,6 @@ package org.mage.test.cards.single.ncc; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.keyword.SuspendAbility; import mage.constants.PhaseStep; -import mage.constants.TimingRule; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; @@ -29,6 +26,7 @@ public class SinisterConciergeTest extends CardTestPlayerBase { */ @Test public void testWorking() { + // TODO: remove multiple calls to execute() addCard(Zone.HAND, playerA, lightningBolt); addCard(Zone.BATTLEFIELD, playerA, sinisterConcierge); addCard(Zone.BATTLEFIELD, playerA, "Mountain"); @@ -59,12 +57,14 @@ public class SinisterConciergeTest extends CardTestPlayerBase { assertExileCount(playerB, bondedConstruct, 1); assertCounterOnExiledCardCount(bondedConstruct, CounterType.TIME, 1); + setChoice(playerB, true); // yes to cast setStopAt(6, PhaseStep.PRECOMBAT_MAIN); execute(); assertExileCount(playerA, sinisterConcierge, 1); assertExileCount(playerB, bondedConstruct, 0); assertPermanentCount(playerB, bondedConstruct, 1); + setChoice(playerA, true); // yes to cast setStopAt(7, PhaseStep.PRECOMBAT_MAIN); execute(); assertExileCount(playerA, sinisterConcierge, 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/nec/MyojinOfGrimBetrayalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/nec/MyojinOfGrimBetrayalTest.java new file mode 100644 index 00000000000..85b8ced6872 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/nec/MyojinOfGrimBetrayalTest.java @@ -0,0 +1,71 @@ +package org.mage.test.cards.single.nec; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.m.MyojinOfGrimBetrayal Myojin of Grim Betrayal} {5}{B}{B}{B}{B} + * Legendary Creature - Spirit 5/2 + * Myojin of Grim Betrayal enters the battlefield with an indestructible counter on it if you cast it from your hand. + * Remove an indestructible counter from Myojin of Grim Betrayal: + * Put onto the battlefield under your control all creature cards in all graveyards that were put there from anywhere this turn. + * + * Issue #11721 - The second ability of Myojin was only returning + * creatures that were put into the graveyard from the battlefield. + * + * @author DominionSpy + */ +public class MyojinOfGrimBetrayalTest extends CardTestPlayerBase { + + @Test + public void test_MyojinOfGrimBetrayal() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 9 + 2 + 1 + 2 + 4); + addCard(Zone.BATTLEFIELD, playerA, "Vampire Hexmage"); + addCard(Zone.BATTLEFIELD, playerA, "Thrull Wizard"); + addCard(Zone.EXILED, playerB, "Llanowar Elves"); + + addCard(Zone.HAND, playerA, "Myojin of Grim Betrayal"); + addCard(Zone.HAND, playerA, "Archfiend of Ifnir"); + addCard(Zone.HAND, playerA, "Banehound"); + addCard(Zone.HAND, playerA, "Mind Raker"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Myojin of Grim Betrayal"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Creature put into graveyard from battlefield (Vampire Hexmage) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice", "Thrull Wizard"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Creature put into graveyard from hand (Archfiend of Ifnir) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // Creature put into graveyard from stack (Banehound) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banehound"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{B}", "Banehound"); + setChoice(playerA, false); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + showExile("exile b", 1, PhaseStep.PRECOMBAT_MAIN, playerB); + showGraveyard("grave b", 1, PhaseStep.PRECOMBAT_MAIN, playerB); + + // Creature put into graveyard from exile (Llanowar Elves) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mind Raker"); + setChoice(playerA, true); + addTarget(playerA, "Llanowar Elves"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Vampire Hexmage", 1); + assertPermanentCount(playerA, "Archfiend of Ifnir", 1); + assertPermanentCount(playerA, "Banehound", 1); + assertPermanentCount(playerA, "Llanowar Elves", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/rna/TeysaKarlovTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/rna/TeysaKarlovTest.java new file mode 100644 index 00000000000..eafb17ad335 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/rna/TeysaKarlovTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.single.rna; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class TeysaKarlovTest extends CardTestPlayerBase { + + /* Teysa Karlov {2}{W}{B} 2/4 + * Legendary Creature — Human Advisor + * If a creature dying causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. + * Creature tokens you control have vigilance and lifelink. + */ + private static final String teysa = "Teysa Karlov"; + + /* Revel in Riches {4}{B} + * Enchantment + * Whenever a creature an opponent controls dies, create a Treasure token. + * At the beginning of your upkeep, if you control ten or more Treasures, you win the game. + */ + private static final String revel = "Revel in Riches"; + + private static final String bloodthrone = "Bloodthrone Vampire"; // 1/1 + // Sacrifice a creature: Bloodthrone Vampire gets +2/+2 until end of turn. + + private static final String ministrant = "Ministrant of Obligation"; // 2/1 Afterlife 2 + + @Test + public void testTriggers() { + addCard(Zone.BATTLEFIELD, playerA, teysa); + addCard(Zone.BATTLEFIELD, playerB, revel); + addCard(Zone.BATTLEFIELD, playerA, bloodthrone); + addCard(Zone.BATTLEFIELD, playerA, ministrant); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, ministrant); + setChoice(playerA, "Afterlife"); // order identical triggers + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, bloodthrone, 3, 3); + assertGraveyardCount(playerA, ministrant, 1); + assertPermanentCount(playerA, "Spirit Token", 4); + assertPermanentCount(playerB, "Treasure Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/text/WrennAndSixTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/text/WrennAndSixTest.java index 21a30a73caa..e584e3a7e57 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/text/WrennAndSixTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/text/WrennAndSixTest.java @@ -9,7 +9,7 @@ public class WrennAndSixTest { @Test public void testFirstLoyaltyAbilityRulesText() { - Card wrennAndSix = CardRepository.instance.findCard("Wrenn and Six").getCard(); + Card wrennAndSix = CardRepository.instance.findCard("Wrenn and Six").createCard(); String firstLoyaltyAbilityRulesText = wrennAndSix.getRules().get(0); Assert.assertEquals(firstLoyaltyAbilityRulesText, "+1: Return up to one target land card from your graveyard to your hand."); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/DonnaNobleTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/DonnaNobleTests.java new file mode 100644 index 00000000000..826afcc4757 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/DonnaNobleTests.java @@ -0,0 +1,138 @@ +package org.mage.test.cards.triggers.damage; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author jimga150 + */ +public class DonnaNobleTests extends CardTestCommander4Players { + + @Test + public void PairedCreature2To1Test() { + + //Check that paired creature being dealt damage by 2 sources at the same time = 1 trigger with correct amount + + addCard(Zone.BATTLEFIELD, playerA, "Donna Noble", 1); // Legendary Creature — Human 2/4 {3}{R} + + addCard(Zone.HAND, playerA, "Impervious Greatwurm", 1); // Creature — Wurm 16/16 {7}{G}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Impervious Greatwurm", true); + + //Yes, soul bond donna noble with IG + setChoice(playerA, "Yes"); + + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // Artifact Creature — Construct 1/1 {0} + addCard(Zone.BATTLEFIELD, playerB, "Expedition Envoy", 1); // Creature — Human Scout Ally 2/1 {W} + + attack(5, playerA, "Impervious Greatwurm", playerB); + block(5, playerB, "Memnite", "Impervious Greatwurm"); + block(5, playerB, "Expedition Envoy", "Impervious Greatwurm"); + + //Assign this much damage to the first blocking creature + setChoice(playerA, "X=1"); + + //Assign this much damage to the second blocking creature + setChoice(playerA, "X=1"); + + //Target this player with Donna Noble + addTarget(playerA, playerB); + + setStopAt(5, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife() - 3); + + } + + @Test + public void DonnaAndPairedBothDamagedSingleSourceTest() { + + //Check that Donna and paired creature both damaged at the same time by one source = 2 triggers with correct amounts + + addCard(Zone.BATTLEFIELD, playerA, "Donna Noble", 1); // Legendary Creature — Human 2/4 {3}{R} + + addCard(Zone.HAND, playerA, "Impervious Greatwurm", 1); // Creature — Wurm 16/16 {7}{G}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Impervious Greatwurm", true); + + //Yes, soul bond donna noble with IG + setChoice(playerA, "Yes"); + + // Kicker {R} (You may pay an additional {R} as you cast this spell.) + // Cinderclasm deals 1 damage to each creature. If it was kicked, it deals 2 damage to each creature instead. + addCard(Zone.HAND, playerA, "Cinderclasm", 1); // Instant {1}{R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cinderclasm", true); + + //Yes, pay kicker for Cinderclasm + setChoice(playerA, "Yes"); + + //pick triggered ability starting with this string to enter the stack first + setChoice(playerA, "Whenever"); + + //Target this player with Donna Noble + addTarget(playerA, playerB); + + //Target this player with Donna Noble (second trigger) + addTarget(playerA, playerB); + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife() - 4); + + } + + @Test + public void DonnaAndPairedBothDamagedDiffSourceTest() { + + //Check that Donna and paired creature both damaged at the same time by different sources = 2 triggers with correct amounts + + addCard(Zone.BATTLEFIELD, playerA, "Donna Noble", 1); // Legendary Creature — Human 2/4 {3}{R} + + addCard(Zone.HAND, playerA, "Impervious Greatwurm", 1); // Creature — Wurm 16/16 {7}{G}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Impervious Greatwurm", true); + + //Yes, soul bond donna noble with IG + setChoice(playerA, "Yes"); + + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // Artifact Creature — Construct 1/1 {0} + addCard(Zone.BATTLEFIELD, playerB, "Expedition Envoy", 1); // Creature — Human Scout Ally 2/1 {W} + + attack(4, playerB, "Memnite", playerA); + attack(4, playerB, "Expedition Envoy", playerA); + block(4, playerA, "Impervious Greatwurm", "Memnite"); + block(4, playerA, "Donna Noble", "Expedition Envoy"); + + //pick triggered ability starting with this string to enter the stack first + setChoice(playerA, "Whenever"); + + //Target this player with Donna Noble + addTarget(playerA, playerB); + + //Target this player with Donna Noble (second trigger) + addTarget(playerA, playerB); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife() - 3); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/DamagedBatchTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/DamagedBatchTests.java new file mode 100644 index 00000000000..e4c30fd1535 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/DamagedBatchTests.java @@ -0,0 +1,71 @@ + +package org.mage.test.cards.triggers.delayed; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author jimga150 + */ +public class DamagedBatchTests extends CardTestCommander4Players { + + @Test + public void DamageBatchForOnePermanent2To1Test() { + + //Check that one creature being dealt damage by 2 sources at the same time = 1 trigger of DAMAGED_BATCH_FOR_ONE_PERMANENT + + addCard(Zone.BATTLEFIELD, playerA, "Donna Noble", 1); // Legendary Creature — Human 2/4 {3}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); // Artifact Creature — Construct 1/1 {0} + addCard(Zone.BATTLEFIELD, playerB, "Expedition Envoy", 1); // Creature — Human Scout Ally 2/1 {W} + + attack(1, playerA, "Donna Noble", playerB); + block(1, playerB, "Memnite", "Donna Noble"); + block(1, playerB, "Expedition Envoy", "Donna Noble"); + + //Assign this much damage to the first blocking creature + setChoice(playerA, "X=1"); + + //Target this player with Donna Noble + addTarget(playerA, playerB); + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife() - 3); + + } + + @Test + public void DamageBatchForOnePermanent2EventTest() { + + //Check that one creature being dealt damage at 2 different times (double strike in this case) = 2 triggers of DAMAGED_BATCH_FOR_ONE_PERMANENT + + addCard(Zone.BATTLEFIELD, playerA, "Donna Noble", 1); // Legendary Creature — Human 2/4 {3}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Adorned Pouncer", 1); // Creature — Cat 1/1 {1}{W} + + attack(1, playerA, "Donna Noble", playerB); + block(1, playerB, "Adorned Pouncer", "Donna Noble"); + + //Target this player with Donna Noble + addTarget(playerA, playerB); + + //Target this player with Donna Noble (second trigger) + addTarget(playerA, playerB); + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife() - 2); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/OnduRisingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/OnduRisingTest.java index f6db3189ffd..c13c1b292db 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/OnduRisingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/OnduRisingTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers.delayed; import mage.constants.PhaseStep; @@ -12,34 +11,77 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class OnduRisingTest extends CardTestPlayerBase { + /** Ondu Rising {1}{W} Sorcery + * Whenever a creature attacks this turn, it gains lifelink until end of turn. + * Awaken 4—{4}{W} (If you cast this spell for {4}{W}, also put four +1/+1 counters on target land you control and it becomes a 0/0 Elemental creature with haste. It’s still a land.) + */ + private static final String onduRising = "Ondu Rising"; + private static final String lion = "Silvercoat Lion"; // 2/2 + private static final String doomBlade = "Doom Blade"; + private static final String addW = "{T}: Add {W}"; + @Test - public void testLiflinkGained() { + public void testLifelinkGained() { addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - // Whenever a creature attacks this turn, it gains lifelink until end of turn. - // Awaken 4—{4}{W} - addCard(Zone.HAND, playerB, "Ondu Rising", 1); + addCard(Zone.BATTLEFIELD, playerB, lion, 1); + addCard(Zone.HAND, playerB, onduRising, 1); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {W}"); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {W}"); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {W}"); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {W}"); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {W}"); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ondu Rising with awaken"); + addTarget(playerB, "Mountain"); - attack(2, playerB, "Silvercoat Lion"); - attack(2, playerB, "Mountain"); + attack(2, playerB, "Silvercoat Lion", playerA); + attack(2, playerB, "Mountain", playerA); + setChoice(playerB, "Whenever "); // order triggers + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); - assertGraveyardCount(playerB, "Ondu Rising", 1); + assertGraveyardCount(playerB, onduRising, 1); assertPowerToughness(playerB, "Mountain", 4, 4); - assertLife(playerA, 14); assertLife(playerB, 26); } + @Test + public void testNoFizzle() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerB, lion, 1); + addCard(Zone.HAND, playerB, onduRising, 1); + addCard(Zone.HAND, playerA, doomBlade, 1); + + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, addW); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ondu Rising with awaken"); + addTarget(playerB, "Mountain"); + + attack(2, playerB, "Silvercoat Lion", playerA); + attack(2, playerB, "Mountain", playerA); + setChoice(playerB, "Whenever "); // order triggers + castSpell(2, PhaseStep.DECLARE_ATTACKERS, playerA, doomBlade, "Mountain"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerB, onduRising, 1); + assertGraveyardCount(playerB, "Mountain", 1); + assertGraveyardCount(playerA, doomBlade, 1); + assertLife(playerA, 18); + assertLife(playerB, 22); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java index 2ab600627f7..53656cefbb3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java @@ -74,7 +74,7 @@ public class CommanderColorIdentityTest extends CardTestCommander3PlayersFFA { if (cardInfo == null) { throw new IllegalArgumentException("Couldn't find the card " + cardName + " in the DB."); } - Card card = cardInfo.getCard(); + Card card = cardInfo.createCard(); FilterMana filterMana = card.getColorIdentity(); return filterMana.toString(); } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 04c0f9739fe..f4886175e37 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -183,8 +183,11 @@ public class TestPlayer implements Player { targets.add(target); } - public void addAlias(String name, UUID Id) { - aliases.put(name, Id); + public void addAlias(String aliasId, UUID objectId) { + if (aliases.containsKey(aliasId)) { + throw new IllegalArgumentException("Alias with same aliasId already exists: " + aliasId); + } + aliases.put(aliasId, objectId); } public ManaOptions getAvailableManaTest(Game game) { @@ -1216,6 +1219,7 @@ public class TestPlayer implements Player { .map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ") + c.getIdName() + (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "") + + " class " + c.getMainCard().getClass().getSimpleName() + "" + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue() + (c.isPlaneswalker(game) ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "") + ", " + (c.isTapped() ? "Tapped" : "Untapped") diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java index 100e71737d1..e8094c9c2dc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java @@ -86,7 +86,7 @@ public class AbilityPickerTest extends CardTestPlayerBase { private Abilities getAbilitiesFromCard(String cardName) { CardInfo info = CardRepository.instance.findCard(cardName); - PermanentImpl permanent = new PermanentCard(info.getCard(), playerA.getId(), currentGame); + PermanentImpl permanent = new PermanentCard(info.createCard(), playerA.getId(), currentGame); return permanent.getAbilities(currentGame); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java index 851a17c7271..d78351933ca 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java @@ -2,21 +2,26 @@ package org.mage.test.serverside; import mage.MageObject; import mage.MageObjectImpl; +import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.Card; import mage.cards.repository.TokenRepository; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.SubType; import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.HumanToken; import mage.game.permanent.token.SoldierToken; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.custom.CreatureToken; +import mage.game.stack.Spell; import mage.util.CardUtil; import mage.view.CardView; import mage.view.GameView; @@ -162,9 +167,7 @@ public class TokenImagesTest extends CardTestPlayerBase { // collect real client stats Map> realClientStats = new LinkedHashMap<>(); GameView gameView = new GameView(currentGame.getState(), currentGame, playerA.getId(), null); - PlayerView playerView = gameView.getPlayers().stream().filter(p -> p.getName().equals(playerA.getName())).findFirst().orElse(null); - Assert.assertNotNull(playerView); - playerView.getBattlefield().values() + gameView.getMyPlayer().getBattlefield().values() .stream() .filter(card -> card.getName().equals(tokenName)) .filter(CardView::isToken) @@ -261,26 +264,20 @@ public class TokenImagesTest extends CardTestPlayerBase { } } - private void assert_TokenImageNumber(String tokenName, List needUniqueImages) { + private void assert_TokenOrCardImageNumber(String tokenOrCardName, List needUniqueImages) { Set serverStats = currentGame.getBattlefield().getAllPermanents() .stream() - .filter(card -> card.getName().equals(tokenName)) - .filter(card -> card instanceof PermanentToken) + .filter(card -> card.getName().equals(tokenOrCardName)) + .filter(card -> card instanceof MageObjectImpl) .sorted(Comparator.comparing(Card::getExpansionSetCode)) - .map(card -> (PermanentToken) card) + .map(card -> (MageObjectImpl) card) .map(MageObjectImpl::getImageNumber) .collect(Collectors.toSet()); GameView gameView = new GameView(currentGame.getState(), currentGame, playerA.getId(), null); - PlayerView playerView = gameView.getPlayers() + Set clientStats = gameView.getMyPlayer().getBattlefield().values() .stream() - .filter(p -> p.getName().equals(playerA.getName())) - .findFirst() - .orElse(null); - Assert.assertNotNull(playerView); - Set clientStats = playerView.getBattlefield().values() - .stream() - .filter(card -> card.getName().equals(tokenName)) + .filter(card -> card.getName().equals(tokenOrCardName)) .sorted(Comparator.comparing(CardView::getExpansionSetCode)) .map(CardView::getImageNumber) .collect(Collectors.toSet()); @@ -289,8 +286,79 @@ public class TokenImagesTest extends CardTestPlayerBase { String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); - Assert.assertEquals(imagesNeed, imagesServer); - Assert.assertEquals(imagesNeed, imagesClient); + Assert.assertEquals("server side", imagesNeed, imagesServer); + Assert.assertEquals("client side", imagesNeed, imagesClient); + } + + private void assertFaceDownCharacteristics(String info, MageObject object, String faceDownTypeName) { + String prefix = info + " - " + object; + + // image info + Assert.assertEquals(prefix + " - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, object.getExpansionSetCode()); + Assert.assertEquals(prefix + " - wrong card number", "0", object.getCardNumber()); + Assert.assertEquals(prefix + " - wrong image file name", faceDownTypeName, object.getImageFileName()); + Assert.assertNotEquals(prefix + " - wrong image number", Integer.valueOf(0), object.getImageNumber()); + + // characteristic checks instead new test + Assert.assertEquals(prefix + " - wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), object.getName()); + Assert.assertEquals(prefix + " - wrong power", 2, object.getPower().getValue()); + Assert.assertEquals(prefix + " - wrong toughness", 2, object.getToughness().getValue()); + Assert.assertEquals(prefix + " - wrong color", "", object.getColor(currentGame).toString()); + Assert.assertEquals(prefix + " - wrong supertypes", "[]", object.getSuperType(currentGame).toString()); + Assert.assertEquals(prefix + " - wrong types", "[Creature]", object.getCardType(currentGame).toString()); + Assert.assertEquals(prefix + " - wrong subtypes", "[]", object.getSubtype(currentGame).toString()); + Assert.assertTrue(prefix + " - wrong abilities", object.getAbilities().stream().anyMatch(a -> !CardUtil.isInformationAbility(a))); // become face down + face up abilities only + } + + private void assertOriginalData(String info, CardView cardView, int needPower, int needToughness, String needColor) { + String prefix = info + " - " + cardView; + int currentPower = cardView.getOriginalPower() == null ? 0 : cardView.getOriginalPower().getValue(); + int currentToughness = cardView.getOriginalToughness() == null ? 0 : cardView.getOriginalToughness().getValue(); + Assert.assertEquals(prefix + " - wrong power", needPower, currentPower); + Assert.assertEquals(prefix + " - wrong toughness", needToughness, currentToughness); + if (needColor != null) { + Assert.assertEquals(prefix + " - wrong color", needColor, cardView.getOriginalColorIdentity()); + } + } + + private void assert_FaceDownMorphImageNumber(List needUniqueImages) { + Set serverStats = currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(card -> card.isFaceDown(currentGame)) + .filter(card -> { + Assert.assertEquals("server side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode()); + return true; + }) + .sorted(Comparator.comparing(Card::getExpansionSetCode)) + .map(card -> (MageObjectImpl) card) + .map(MageObjectImpl::getImageNumber) + .collect(Collectors.toSet()); + + // use another player to hide card view names in face down + GameView gameView = new GameView(currentGame.getState(), currentGame, playerB.getId(), null); + PlayerView playerView = gameView.getPlayers() + .stream() + .filter(p -> p.getName().equals(playerA.getName())) + .findFirst() + .orElse(null); + Assert.assertNotNull(playerView); + Set clientStats = playerView.getBattlefield().values() + .stream() + .filter(CardView::isFaceDown) + .filter(card -> { + Assert.assertEquals("client side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode()); + return true; + }) + .sorted(Comparator.comparing(CardView::getExpansionSetCode)) + .map(CardView::getImageNumber) + .collect(Collectors.toSet()); + + // server and client sides must have same data + String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); + String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); + String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", ")); + Assert.assertEquals("server side", imagesNeed, imagesServer); + Assert.assertEquals("client side", imagesNeed, imagesClient); } @Test @@ -317,7 +385,7 @@ public class TokenImagesTest extends CardTestPlayerBase { // x2 tokens assert_MemorialToGlory(20, "40K=40"); - assert_TokenImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers + assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers } @Test @@ -381,7 +449,7 @@ public class TokenImagesTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, 1 + 10); // 1 test card + 10 tokens - assert_TokenImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image + assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image assert_Inner("test", 0, 0, 1, "Soldier Token", 10, false, "40K=10"); } @@ -479,7 +547,7 @@ public class TokenImagesTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assert_TokenImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image + assert_TokenOrCardImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image assert_Inner("test", 0, 0, 1, "Human Token", 10, false, "MOC=10"); } @@ -614,9 +682,9 @@ public class TokenImagesTest extends CardTestPlayerBase { @Test // it's ok for fail in 1 of 50 // TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo - // (see setFlipCoinResult and setDieRollResult), so no needs in big amout + // (see setFlipCoinResult and setDieRollResult), so no needs in big amount public void test_Abilities_Incubator_MustTransformWithSameSettings() { - // bug with miss image data in tranformed incubator token: https://github.com/magefree/mage/issues/11535 + // bug with miss image data in transformed incubator token: https://github.com/magefree/mage/issues/11535 // make sure random images take all 3 diff images int needIncubatorTokens = 30; @@ -656,8 +724,261 @@ public class TokenImagesTest extends CardTestPlayerBase { "Phyrexian Token", needPhyrexianTokens, false, "MOM=" + needPhyrexianTokens); // MOM-Incubator has 1 image (number is 0) - assert_TokenImageNumber("Incubator Token", Arrays.asList(0)); + assert_TokenOrCardImageNumber("Incubator Token", Arrays.asList(0)); // MOM-Phyrexian has 3 images - assert_TokenImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3)); + assert_TokenOrCardImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3)); + } + + @Test // it's ok for fail in very rare random + // TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo + // (see setFlipCoinResult and setDieRollResult), so no needs in big amount + public void test_FaceDown_CardWithMorph_MustGetDefaultImage() { + int faceDownAmount = 15; + addCard(Zone.HAND, playerA, "Ainok Tracker", faceDownAmount); // {5}{R}, Morph {4}{R}, face up {3} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5 * faceDownAmount); + + IntStream.range(0, faceDownAmount).forEach(i -> { + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ainok Tracker using Morph"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount); + assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3)); + } + + @Test // it's ok for fail in very rare random + public void test_FaceDown_LandWithMorph_MustGetDefaultImage() { + int faceDownAmount = 15; + addCard(Zone.HAND, playerA, "Zoetic Cavern", faceDownAmount); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 * faceDownAmount); + + IntStream.range(0, faceDownAmount).forEach(i -> { + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount); + assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3)); + } + + @Test + public void test_FaceDown_Spell() { + addCard(Zone.HAND, playerA, "Zoetic Cavern", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph"); + runCode("stack check", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Assert.assertEquals("stack must be active", 1, game.getState().getStack().size()); + + // server side spell before resolve contains full info, not empty + // so real data will be full, but view data will be hidden by face down status + String cardName = "Zoetic Cavern"; + String needClientControllerName = CardUtil.getCardNameForGUI(cardName, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH); + String needClientOpponentName = CardUtil.getCardNameForGUI("", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH); + + Spell spell = (Spell) game.getState().getStack().stream().findFirst().orElse(null); + Assert.assertNotNull("server - spell must exists", spell); + + // make sure image from object's id works fine + IntStream.of(5).forEach(i -> { + UUID objectId = UUID.randomUUID(); + int objectImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber(); + Assert.assertNotEquals("wrong image number", 0, objectImageNumber); + IntStream.of(5).forEach(j -> { + int newImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber(); + Assert.assertEquals("generated image numbers must be same for same id", objectImageNumber, newImageNumber); + }); + }); + + // debug + //CardView debugViewOpponent = new CardView(spell, currentGame, false, false); + //CardView debugViewController = new CardView(spell, currentGame, true, false); + + // server side (full data) + Assert.assertTrue("server - wrong face down status", spell.isFaceDown(game)); + Assert.assertEquals("server - wrong color", spell.getColor(game), new ObjectColor()); + Assert.assertEquals("server - wrong name", cardName, spell.getName()); + // + // workaround to find image number (from id) - it must be same on each generate + int serverImageNumber = spell.getSpellAbility().getCharacteristics(game).getImageNumber(); + Assert.assertNotEquals("server - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spell.getExpansionSetCode()); + Assert.assertNotEquals("server - wrong image number", 0, serverImageNumber); + + // client side - controller (hidden + card name) + GameView gameView = getGameView(playerA); + CardView spellView = gameView.getStack().values().stream().findFirst().orElse(null); + Assert.assertNotNull("client, controller - spell must exists", spellView); + Assert.assertTrue("client, controller - wrong face down status", spellView.isFaceDown()); + Assert.assertEquals("client, controller - wrong color", spellView.getColor(), new ObjectColor()); + Assert.assertEquals("client, controller - wrong spell name", needClientControllerName, spellView.getName()); + // + Assert.assertEquals("client, controller - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode()); + Assert.assertEquals("client, controller - wrong card number", "0", spellView.getCardNumber()); + Assert.assertEquals("client, controller - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName()); + Assert.assertEquals("client, controller - wrong image number", serverImageNumber, spellView.getImageNumber()); + + // client side - opponent (hidden) + gameView = getGameView(playerB); + spellView = gameView.getStack().values().stream().findFirst().orElse(null); + Assert.assertNotNull("client, opponent - spell must exists", spellView); + Assert.assertTrue("client, opponent - wrong face down status", spellView.isFaceDown()); + Assert.assertEquals("client, opponent - wrong color", spellView.getColor(), new ObjectColor()); + Assert.assertEquals("client, opponent - wrong spell name", needClientOpponentName, spellView.getName()); + // + Assert.assertEquals("client, opponent - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode()); + Assert.assertEquals("client, opponent - wrong card number", "0", spellView.getCardNumber()); + Assert.assertEquals("client, opponent - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName()); + Assert.assertEquals("client, opponent - wrong image number", serverImageNumber, spellView.getImageNumber()); + }); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + } + + @Test + public void test_FaceDown_Megamorph_MustGetDefaultImage() { + addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6 + 3); + + // prepare face down permanent + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Morph"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, "Aerie Bowmasters", 0); + Permanent permanent = getPermanent(EmptyNames.FACE_DOWN_CREATURE.toString(), playerA); + assertFaceDownCharacteristics("permanent", permanent, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH); + }); + + // face up it and find counter + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn this"); + runCode("on face up", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, "Aerie Bowmasters", 1); + assertCounterCount(playerA, "Aerie Bowmasters", CounterType.P1P1, 1); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_FaceDown_ExileZone_MustGetDefaultImage() { + // {T}: Draw a card, then exile a card from your hand face down. + addCard(Zone.BATTLEFIELD, playerA, "Bane Alley Broker", 1); + addCard(Zone.HAND, playerA, "Forest", 1); + + // exile face down + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw a card"); + addTarget(playerA, "Forest"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // check face down card in exile + runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0); + GameView gameView = getGameView(playerA); + CardView controllerCardView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + gameView = getGameView(playerB); + CardView opponentCardView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + + // server side (full data) + // TODO: possible bugged?! Other abilities must not see faced-down card as real on server side! + String needName = "Forest"; + Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame)); + Assert.assertEquals("server side - wrong name", needName, card.getName()); + Assert.assertTrue("server side - wrong abilities", card.getAbilities(currentGame).stream().anyMatch(a -> !CardUtil.isInformationAbility(a))); // play + add mana + + // client side - controller (hidden data + original name) + needName = "Face Down: Forest"; + Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName()); + Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown()); + Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size()); + assertOriginalData("controller, original data", controllerCardView, 0, 0, ""); + + // client side - opponent (hidden data) + needName = "Face Down"; + Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown()); + Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName()); + Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size()); + assertOriginalData("opponent, original data", opponentCardView, 0, 0, ""); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_FaceDown_ForetellInExile_MustGetDefaultImage() { + // Foretell {1}{U} + addCard(Zone.HAND, playerA, "Behold the Multiverse", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // exile face down as foretell + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}{U}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // check face down card + runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0); + GameView gameView = getGameView(playerA); + CardView controllerCardView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + gameView = getGameView(playerB); + CardView opponentCardView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + + // server side (full data) + // TODO: possible bugged?! Other abilities must not see faced-down card as real on server side! + String needName = "Behold the Multiverse"; + Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame)); + Assert.assertEquals("server side - wrong name", needName, card.getName()); + Assert.assertTrue("server side - wrong abilities", card.getAbilities(currentGame).stream().anyMatch(a -> !CardUtil.isInformationAbility(a))); + + // client side - controller (hidden data + original name) + needName = "Foretell: Behold the Multiverse"; + Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName()); + Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown()); + Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size()); + assertOriginalData("controller, original data", controllerCardView, 0, 0, ""); + + // client side - opponent (hidden data) + needName = "Foretell"; + Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown()); + Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName()); + Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size()); + assertOriginalData("opponent, original data", opponentCardView, 0, 0, ""); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java deleted file mode 100644 index 9225f017a5d..00000000000 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ /dev/null @@ -1,302 +0,0 @@ -package org.mage.test.serverside.base; - -import mage.cards.Card; -import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.match.MatchType; -import mage.game.permanent.PermanentCard; -import mage.game.tournament.TournamentType; -import mage.players.Player; -import mage.players.PlayerType; -import mage.server.game.GameFactory; -import mage.server.game.PlayerFactory; -import mage.server.managers.ConfigSettings; -import mage.server.tournament.TournamentFactory; -import mage.server.util.ConfigFactory; -import mage.server.util.ConfigWrapper; -import mage.server.util.PluginClassLoader; -import mage.server.util.config.GamePlugin; -import mage.server.util.config.Plugin; -import mage.util.CardUtil; -import mage.util.Copier; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.junit.BeforeClass; -import org.mage.test.player.TestPlayer; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Base class for all tests. - * - * @author ayratn - */ -public abstract class MageTestBase { - - protected static Logger logger = Logger.getLogger(MageTestBase.class); - - public static PluginClassLoader classLoader = new PluginClassLoader(); - - private static final String PLUGIN_FOLDER = "plugins"; - - protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)(:\\{tapped\\})?"); - - protected List handCardsA = new ArrayList<>(); - protected List handCardsB = new ArrayList<>(); - protected List battlefieldCardsA = new ArrayList<>(); - protected List battlefieldCardsB = new ArrayList<>(); - protected List graveyardCardsA = new ArrayList<>(); - protected List graveyardCardsB = new ArrayList<>(); - protected List libraryCardsA = new ArrayList<>(); - protected List libraryCardsB = new ArrayList<>(); - - protected Map commandsA = new HashMap<>(); - protected Map commandsB = new HashMap<>(); - - protected TestPlayer playerA; - protected TestPlayer playerB; - - /** - * Game instance initialized in load method. - */ - protected static Game currentGame = null; - - /** - * Player thats starts the game first. By default, it is ComputerA. - */ - protected static Player activePlayer = null; - - protected Integer stopOnTurn; - - protected PhaseStep stopAtStep = PhaseStep.UNTAP; - - protected enum ParserState { - - INIT, - OPTIONS, - EXPECTED - } - - protected ParserState parserState; - - /** - * Expected results of the test. Read from test case in {@link String} based - * format: - *

    - * Example: turn:1 result:won:ComputerA life:ComputerA:20 life:ComputerB:0 - * battlefield:ComputerB:Tine Shrike:0 graveyard:ComputerB:Tine Shrike:1 - */ - protected List expectedResults = new ArrayList<>(); - - protected static final String TESTS_PATH = "tests" + File.separator; - - @BeforeClass - public static void init() { - Logger.getRootLogger().setLevel(Level.DEBUG); - - // one time init for all tests - if (GameFactory.instance.getGameTypes().isEmpty()) { - deleteSavedGames(); - ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); - config.getGameTypes().forEach((gameType) -> { - GameFactory.instance.addGameType(gameType.getName(), loadGameType(gameType), loadPlugin(gameType)); - }); - config.getTournamentTypes().forEach((tournamentType) -> { - TournamentFactory.instance.addTournamentType(tournamentType.getName(), loadTournamentType(tournamentType), loadPlugin(tournamentType)); - }); - config.getPlayerTypes().forEach((playerType) -> { - PlayerFactory.instance.addPlayerType(playerType.getName(), loadPlugin(playerType)); - }); -// for (Plugin plugin : config.getDeckTypes()) { -// DeckValidatorFactory.getInstance().addDeckType(plugin.getName(), loadPlugin(plugin)); -// } - Copier.setLoader(classLoader); - } - } - - @SuppressWarnings("UseSpecificCatch") - private static Class loadPlugin(Plugin plugin) { - try { - classLoader.addURL(new File(PLUGIN_FOLDER + '/' + plugin.getJar()).toURI().toURL()); - logger.debug("Loading plugin: " + plugin.getClassName()); - return Class.forName(plugin.getClassName(), true, classLoader); - } catch (ClassNotFoundException ex) { - logger.warn("Plugin not Found:" + plugin.getJar() + " - check plugin folder"); - } catch (Exception ex) { - logger.fatal("Error loading plugin " + plugin.getJar(), ex); - } - return null; - } - - private static MatchType loadGameType(GamePlugin plugin) { - try { - classLoader.addURL(new File(PLUGIN_FOLDER + '/' + plugin.getJar()).toURI().toURL()); - logger.debug("Loading game type: " + plugin.getClassName()); - return (MatchType) Class.forName(plugin.getTypeName(), true, classLoader).getConstructor().newInstance(); - } catch (ClassNotFoundException ex) { - logger.warn("Game type not found:" + plugin.getJar() + " - check plugin folder", ex); - } catch (Exception ex) { - logger.fatal("Error loading game type " + plugin.getJar(), ex); - } - return null; - } - - private static TournamentType loadTournamentType(GamePlugin plugin) { - try { - classLoader.addURL(new File(PLUGIN_FOLDER + '/' + plugin.getJar()).toURI().toURL()); - return (TournamentType) Class.forName(plugin.getTypeName(), true, classLoader).getConstructor().newInstance(); - } catch (ClassNotFoundException ex) { - logger.warn("Tournament type not found:" + plugin.getJar() + " - check plugin folder"); - } catch (Exception ex) { - logger.fatal("Error loading game type " + plugin.getJar(), ex); - } - return null; - } - - private static void deleteSavedGames() { - File directory = new File("saved/"); - if (!directory.exists()) { - directory.mkdirs(); - } - File[] files = directory.listFiles( - (dir, name) -> name.endsWith(".game") - ); - for (File file : files) { - file.delete(); - } - } - - protected void parseScenario(String filename) throws FileNotFoundException { - parserState = ParserState.INIT; - File f = new File(filename); - try (Scanner scanner = new Scanner(f)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine().trim(); - if (line == null || line.isEmpty() || line.startsWith("#")) { - continue; - } - if (line.startsWith("$include")) { - includeFrom(line); - continue; - } - if (line.startsWith("$expected")) { - parserState = ParserState.EXPECTED; - continue; - } - parseLine(line); - } - } - } - - private void parseLine(String line) { - if (parserState == ParserState.EXPECTED) { - expectedResults.add(line); // just remember for future use - return; - } - - Matcher m = pattern.matcher(line); - if (m.matches()) { - - String zone = m.group(1); - String nickname = m.group(2); - - if (nickname.equals("ComputerA") || nickname.equals("ComputerB")) { - List cards = null; - List perms = null; - Zone gameZone; - if ("hand".equalsIgnoreCase(zone)) { - gameZone = Zone.HAND; - cards = nickname.equals("ComputerA") ? handCardsA : handCardsB; - } else if ("battlefield".equalsIgnoreCase(zone)) { - gameZone = Zone.BATTLEFIELD; - perms = nickname.equals("ComputerA") ? battlefieldCardsA : battlefieldCardsB; - } else if ("graveyard".equalsIgnoreCase(zone)) { - gameZone = Zone.GRAVEYARD; - cards = nickname.equals("ComputerA") ? graveyardCardsA : graveyardCardsB; - } else if ("library".equalsIgnoreCase(zone)) { - gameZone = Zone.LIBRARY; - cards = nickname.equals("ComputerA") ? libraryCardsA : libraryCardsB; - } else if ("player".equalsIgnoreCase(zone)) { - String command = m.group(3); - if ("life".equals(command)) { - if (nickname.equals("ComputerA")) { - commandsA.put(Zone.OUTSIDE, "life:" + m.group(4)); - } else { - commandsB.put(Zone.OUTSIDE, "life:" + m.group(4)); - } - } - return; - } else { - return; // go parse next line - } - - String cardName = m.group(3); - Integer amount = Integer.parseInt(m.group(4)); - boolean tapped = m.group(5) != null && m.group(5).equals(":{tapped}"); - - if (cardName.equals("clear")) { - if (nickname.equals("ComputerA")) { - commandsA.put(gameZone, "clear"); - } else { - commandsB.put(gameZone, "clear"); - } - } else { - for (int i = 0; i < amount; i++) { - CardInfo cardInfo = CardRepository.instance.findCard(cardName); - Card newCard = cardInfo != null ? cardInfo.getCard() : null; - if (newCard != null) { - if (gameZone == Zone.BATTLEFIELD) { - Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); - PermanentCard p = new PermanentCard(permCard, null, currentGame); - p.setTapped(tapped); - perms.add(p); - } else { - cards.add(newCard); - } - } else { - logger.fatal("Couldn't find a card: " + cardName); - logger.fatal("line: " + line); - } - } - } - } else { - logger.warn("Unknown player: " + nickname); - } - } else { - logger.warn("Init string wasn't parsed: " + line); - } - } - - private void includeFrom(String line) throws FileNotFoundException { - String[] params = line.split(" "); - if (params.length == 2) { - String paramName = params[1]; - if (!paramName.contains("..")) { - String includePath = TESTS_PATH + paramName; - File f = new File(includePath); - if (f.exists()) { - parseScenario(includePath); - } else { - logger.warn("Ignored (file doesn't exist): " + line); - } - } else { - logger.warn("Ignored (wrong charactres): " + line); - } - } else { - logger.warn("Ignored (wrong size): " + line); - } - } - - protected Player createPlayer(String name, PlayerType playerType) { - Optional playerOptional = PlayerFactory.instance.createPlayer(playerType, name, RangeOfInfluence.ALL, 5); - return playerOptional.orElseThrow(() -> new NullPointerException("PlayerFactory error - player is not created")); - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 564f621c30c..c11a7c900fe 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -18,13 +18,12 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; +import mage.game.PutToBattlefieldInfo; import mage.game.match.Match; import mage.game.match.MatchType; -import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; import mage.players.Player; import mage.server.game.GameFactory; @@ -40,7 +39,6 @@ import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCardInExile; import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInLibrary; -import mage.util.CardUtil; import mage.util.Copier; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -50,11 +48,8 @@ import org.mage.test.player.TestComputerPlayer; import org.mage.test.player.TestPlayer; import java.io.File; -import java.io.FileNotFoundException; import java.nio.charset.Charset; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Base class for all tests. @@ -69,10 +64,8 @@ public abstract class MageTestPlayerBase { private static final String pluginFolder = "plugins"; - protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)(:\\{tapped\\})?"); - protected Map> handCards = new HashMap<>(); - protected Map> battlefieldCards = new HashMap<>(); + protected Map> battlefieldCards = new HashMap<>(); // cards + additional status like tapped protected Map> graveyardCards = new HashMap<>(); protected Map> libraryCards = new HashMap<>(); protected Map> commandCards = new HashMap<>(); @@ -104,26 +97,6 @@ public abstract class MageTestPlayerBase { protected PhaseStep stopAtStep = PhaseStep.UNTAP; - protected enum ParserState { - - INIT, - OPTIONS, - EXPECTED - } - - protected ParserState parserState; - - /** - * Expected results of the test. Read from test case in {@link String} based - * format: - *

    - * Example: turn:1 result:won:ComputerA life:ComputerA:20 life:ComputerB:0 - * battlefield:ComputerB:Tine Shrike:0 graveyard:ComputerB:Tine Shrike:1 - */ - protected List expectedResults = new ArrayList<>(); - - protected static final String TESTS_PATH = "tests" + File.separator; - @BeforeClass public static void init() { Logger.getRootLogger().setLevel(Level.DEBUG); @@ -193,116 +166,6 @@ public abstract class MageTestPlayerBase { } } - protected void parseScenario(String filename) throws FileNotFoundException { - parserState = ParserState.INIT; - File f = new File(filename); - try (Scanner scanner = new Scanner(f)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine().trim(); - if (line == null || line.isEmpty() || line.startsWith("#")) { - continue; - } - if (line.startsWith("$include")) { - includeFrom(line); - continue; - } - if (line.startsWith("$expected")) { - parserState = ParserState.EXPECTED; - continue; - } - parseLine(line); - } - } - } - - private void parseLine(String line) { - if (parserState == ParserState.EXPECTED) { - expectedResults.add(line); // just remember for future use - return; - } - - Matcher m = pattern.matcher(line); - if (m.matches()) { - - String zone = m.group(1); - String nickname = m.group(2); - - if (nickname.startsWith("Computer")) { - List cards = null; - List perms = null; - Zone gameZone; - if ("hand".equalsIgnoreCase(zone)) { - gameZone = Zone.HAND; - cards = getHandCards(getPlayer(nickname)); - } else if ("battlefield".equalsIgnoreCase(zone)) { - gameZone = Zone.BATTLEFIELD; - perms = getBattlefieldCards(getPlayer(nickname)); - } else if ("graveyard".equalsIgnoreCase(zone)) { - gameZone = Zone.GRAVEYARD; - cards = getGraveCards(getPlayer(nickname)); - } else if ("library".equalsIgnoreCase(zone)) { - gameZone = Zone.LIBRARY; - cards = getLibraryCards(getPlayer(nickname)); - } else if ("command".equalsIgnoreCase(zone)) { - gameZone = Zone.COMMAND; - cards = getCommandCards(getPlayer(nickname)); - } else if ("player".equalsIgnoreCase(zone)) { - String command = m.group(3); - if ("life".equals(command)) { - getCommands(getPlayer(nickname)).put(Zone.OUTSIDE, "life:" + m.group(4)); - } - return; - } else { - return; // go parse next line - } - - String cardName = m.group(3); - Integer amount = Integer.parseInt(m.group(4)); - boolean tapped = m.group(5) != null && m.group(5).equals(":{tapped}"); - - if (cardName.equals("clear")) { - getCommands(getPlayer(nickname)).put(gameZone, "clear"); - } else { - for (int i = 0; i < amount; i++) { - CardInfo cardInfo = CardRepository.instance.findCard(cardName); - Card newCard = cardInfo != null ? cardInfo.getCard() : null; - if (newCard != null) { - if (gameZone == Zone.BATTLEFIELD) { - Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); - PermanentCard p = new PermanentCard(permCard, null, currentGame); - p.setTapped(tapped); - perms.add(p); - } else { - cards.add(newCard); - } - } else { - logger.fatal("Couldn't find a card: " + cardName); - logger.fatal("line: " + line); - } - } - } - } else { - logger.warn("Unknown player: " + nickname); - } - } else { - logger.warn("Init string wasn't parsed: " + line); - } - } - - private TestPlayer getPlayer(String name) { - switch (name) { - case "ComputerA": - return playerA; - case "ComputerB": - return playerB; - case "ComputerC": - return playerC; - case "ComputerD": - return playerD; - } - throw new IllegalArgumentException("Couldn't find player for name=" + name); - } - protected List getHandCards(TestPlayer player) { if (handCards.containsKey(player)) { return handCards.get(player); @@ -339,11 +202,11 @@ public abstract class MageTestPlayerBase { return res; } - protected List getBattlefieldCards(TestPlayer player) { + protected List getBattlefieldCards(TestPlayer player) { if (battlefieldCards.containsKey(player)) { return battlefieldCards.get(player); } - List res = new ArrayList<>(); + List res = new ArrayList<>(); battlefieldCards.put(player, res); return res; } @@ -366,26 +229,6 @@ public abstract class MageTestPlayerBase { return command; } - private void includeFrom(String line) throws FileNotFoundException { - String[] params = line.split(" "); - if (params.length == 2) { - String paramName = params[1]; - if (!paramName.contains("..")) { - String includePath = TESTS_PATH + paramName; - File f = new File(includePath); - if (f.exists()) { - parseScenario(includePath); - } else { - logger.warn("Ignored (file doesn't exist): " + line); - } - } else { - logger.warn("Ignored (wrong charactres): " + line); - } - } else { - logger.warn("Ignored (wrong size): " + line); - } - } - protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { return new TestPlayer(new TestComputerPlayer(name, rangeOfInfluence)); } @@ -437,12 +280,13 @@ public abstract class MageTestPlayerBase { CardSetInfo testSet = new CardSetInfo(needCardName, needSetCode, "123", Rarity.COMMON); Card newCard = new CustomTestCard(controllerPlayer.getId(), testSet, cardType, spellCost); - Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); - PermanentCard permanent = new PermanentCard(permCard, controllerPlayer.getId(), currentGame); switch (putAtZone) { case BATTLEFIELD: - getBattlefieldCards(controllerPlayer).add(permanent); + getBattlefieldCards(controllerPlayer).add(new PutToBattlefieldInfo( + newCard, + false + )); break; case GRAVEYARD: getGraveCards(controllerPlayer).add(newCard); @@ -565,7 +409,7 @@ public abstract class MageTestPlayerBase { } } -// custom card with global abilities list to init (can contains abilities per card name) +// custom card with global abilities list to init (can contain abilities per card name) class CustomTestCard extends CardImpl { static private final Map> abilitiesList = new HashMap<>(); // card name -> abilities @@ -613,9 +457,7 @@ class CustomTestCard extends CardImpl { Set subTypeSet = subTypesList.get(setInfo.getName()); if (subTypeSet != null) { - for (SubType subType : subTypeSet) { - this.subtype.add(subType); - } + this.subtype.addAll(subTypeSet); } if (cardType == CardType.CREATURE) { this.power = new MageInt(1); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 057b2e6143e..d027c6eba5e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -21,7 +21,6 @@ import mage.game.command.CommandObject; import mage.game.command.Emblem; import mage.game.match.MatchOptions; import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentToken; import mage.player.ai.ComputerPlayer7; import mage.player.ai.ComputerPlayerMCTS; @@ -268,7 +267,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement TestPlayer testPlayer = (TestPlayer) player; currentGame.cheat(testPlayer.getId(), getCommands(testPlayer)); currentGame.cheat(testPlayer.getId(), getLibraryCards(testPlayer), getHandCards(testPlayer), - getBattlefieldCards(testPlayer), getGraveCards(testPlayer), getCommandCards(testPlayer)); + getBattlefieldCards(testPlayer), getGraveCards(testPlayer), getCommandCards(testPlayer), + getExiledCards(testPlayer)); + } } @@ -629,7 +630,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } /** - * Add any amount of cards to specified zone of specified player. + * Add any amount of cards to specified zone of specified player without resolve/ETB * * @param gameZone {@link mage.constants.Zone} to add cards to. * @param player {@link Player} to add cards for. Use either playerA or @@ -681,15 +682,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement if (gameZone == Zone.BATTLEFIELD) { for (int i = 0; i < count; i++) { - Card newCard = cardInfo.getCard(); - Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); - - PermanentCard p = new PermanentCard(permCard, player.getId(), currentGame); - p.setTapped(tapped); - getBattlefieldCards(player).add(p); - + Card newCard = cardInfo.createCard(); + getBattlefieldCards(player).add(new PutToBattlefieldInfo( + newCard, + tapped + )); if (!aliasName.isEmpty()) { - player.addAlias(player.generateAliasName(aliasName, useAliasMultiNames, i + 1), p.getId()); + // TODO: is it bugged with double faced cards (wrong ref)? + // add to all players + String aliasId = player.generateAliasName(aliasName, useAliasMultiNames, i + 1); + currentGame.getPlayers().values().forEach(pl -> ((TestPlayer) pl).addAlias(aliasId, newCard.getId())); } } } else { @@ -698,10 +700,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } List cards = getCardList(gameZone, player); for (int i = 0; i < count; i++) { - Card newCard = cardInfo.getCard(); + Card newCard = cardInfo.createCard(); cards.add(newCard); if (!aliasName.isEmpty()) { - player.addAlias(player.generateAliasName(aliasName, useAliasMultiNames, i + 1), newCard.getId()); + // add to all players + String aliasId = player.generateAliasName(aliasName, useAliasMultiNames, i + 1); + currentGame.getPlayers().values().forEach(pl -> ((TestPlayer) pl).addAlias(aliasId, newCard.getId())); } } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java index aad94d5578a..baad13f6a4a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java @@ -2,12 +2,12 @@ package org.mage.test.serverside.deck; import mage.deck.Commander; import org.junit.Test; -import org.mage.test.serverside.base.MageTestBase; +import org.mage.test.serverside.base.MageTestPlayerBase; /** * @author TheElk801 */ -public class CommanderDeckValidationTest extends MageTestBase { +public class CommanderDeckValidationTest extends MageTestPlayerBase { private static final String piper = "The Prismatic Piper"; diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java index aa398d88b13..f11665733cb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CompanionDeckValidationTest.java @@ -2,12 +2,12 @@ package org.mage.test.serverside.deck; import mage.deck.Commander; import org.junit.Test; -import org.mage.test.serverside.base.MageTestBase; +import org.mage.test.serverside.base.MageTestPlayerBase; /** * @author TheElk801 */ -public class CompanionDeckValidationTest extends MageTestBase { +public class CompanionDeckValidationTest extends MageTestPlayerBase { @Test public void testGyrudaTrue() { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidationUtil.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidationUtil.java index 8f8eee99e65..590df6e19fa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidationUtil.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidationUtil.java @@ -69,7 +69,7 @@ public class DeckValidationUtil { } for (int i = 0; i < cardNameAmount.getNumber(); i++) { assert cardinfo != null; - deckToTest.getCards().add(cardinfo.getCard()); + deckToTest.getCards().add(cardinfo.createCard()); } } } @@ -83,7 +83,7 @@ public class DeckValidationUtil { } for (int i = 0; i < cardNameAmount.getNumber(); i++) { assert cardinfo != null; - deckToTest.getSideboard().add(cardinfo.getCard()); + deckToTest.getSideboard().add(cardinfo.createCard()); } } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java index b461b6f9c18..ad9be74689f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java @@ -7,7 +7,7 @@ import mage.deck.Modern; import mage.deck.Standard; import org.junit.Assert; import org.junit.Test; -import org.mage.test.serverside.base.MageTestBase; +import org.mage.test.serverside.base.MageTestPlayerBase; import java.util.ArrayList; @@ -16,7 +16,7 @@ import static org.mage.test.serverside.deck.DeckValidationUtil.testDeckValid; /** * @author LevelX2 */ -public class DeckValidatorTest extends MageTestBase { +public class DeckValidatorTest extends MageTestPlayerBase { @Test public void testStandardDeckCardsAmountValid() { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java index b762e7d609b..3ebd8e0e4ef 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java @@ -41,7 +41,7 @@ public class SerializationTest extends CardTestPlayerBase { @Test public void test_PermanentImpl_Simple() { CardInfo cardInfo = CardRepository.instance.findCard("Balduvian Bears"); - Card newCard = cardInfo.getCard(); + Card newCard = cardInfo.createCard(); Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); PermanentImpl permanent = new PermanentCard(permCard, playerA.getId(), currentGame); currentGame.addPermanent(permanent, 0); @@ -55,7 +55,7 @@ public class SerializationTest extends CardTestPlayerBase { @Test public void test_PermanentImpl_MarkedDamageInfo() { CardInfo cardInfo = CardRepository.instance.findCard("Balduvian Bears"); - Card newCard = cardInfo.getCard(); + Card newCard = cardInfo.createCard(); Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); PermanentImpl permanent = new PermanentCard(permCard, playerA.getId(), currentGame); currentGame.addPermanent(permanent, 0); @@ -77,7 +77,7 @@ public class SerializationTest extends CardTestPlayerBase { private void processSingleCard(CardInfo cardInfo) { // compress each card's part - Card newCard = cardInfo.getCard(); + Card newCard = cardInfo.createCard(); CardUtil.getObjectPartsAsObjects(newCard).stream() .map(Card.class::cast) .forEach(card -> { diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java index a2c6dfe8f13..b932679cb4e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java @@ -17,7 +17,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.mage.test.serverside.base.MageTestBase; +import org.mage.test.serverside.base.MageTestPlayerBase; import java.util.*; import java.util.stream.Collectors; @@ -27,7 +27,7 @@ import static org.junit.Assert.*; /** * @author nigelzor, JayDi85 */ -public class BoosterGenerationTest extends MageTestBase { +public class BoosterGenerationTest extends MageTestPlayerBase { private static final List basics = new ArrayList<>(Arrays.asList("Plains", "Island", "Swamp", "Mountain", "Forest")); diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/AddCardApiTest.java b/Mage.Tests/src/test/java/org/mage/test/testapi/AddCardApiTest.java index 62a79cd6954..873e4a7f18a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/testapi/AddCardApiTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/testapi/AddCardApiTest.java @@ -78,18 +78,42 @@ public class AddCardApiTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Memorial to Glory", 2); - getBattlefieldCards(playerA).stream() - .filter(card -> card.getName().equals("Memorial to Glory")) - .forEach(card -> Assert.assertEquals("40K", card.getExpansionSetCode())); + getBattlefieldCards(playerA) + .stream() + .filter(info -> info.getCard().getName().equals("Memorial to Glory")) + .forEach(info -> Assert.assertEquals("40K", info.getCard().getExpansionSetCode())); assertPermanentCount(playerA, "Plains", 2); - getBattlefieldCards(playerA).stream() - .filter(card -> card.getName().equals("Plains")) - .forEach(card -> Assert.assertEquals("PANA", card.getExpansionSetCode())); + getBattlefieldCards(playerA) + .stream() + .filter(info -> info.getCard().getName().equals("Plains")) + .forEach(info -> Assert.assertEquals("PANA", info.getCard().getExpansionSetCode())); } @Test(expected = org.junit.ComparisonFailure.class) public void test_CardNameWithSetCode_RaiseErrorOnUnknownSet() { addCard(Zone.BATTLEFIELD, playerA, "SS4-Plains", 1); } + + // Add card to exile added for #11738 + @Test + public void test_AddCardExiled() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, "Mind Raker"); + + addCard(Zone.EXILED, playerB, "Llanowar Elves"); + + checkExileCount("llanowar elves in exile", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Llanowar Elves", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mind Raker"); + setChoice(playerA, true); + addTarget(playerA, "Llanowar Elves"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerB, "Llanowar Elves", 0); + assertGraveyardCount(playerB, "Llanowar Elves", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java b/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java index 19b6226953e..4de1ffeba24 100644 --- a/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java @@ -49,8 +49,8 @@ public class AliasesApiTest extends CardTestPlayerBase { Assert.assertFalse(CardUtil.haveSameNames("Name1", "Name2", true)); // name with split card - Card splitCard1 = CardRepository.instance.findCard("Armed // Dangerous").getCard(); - Card splitCard2 = CardRepository.instance.findCard("Alive // Well").getCard(); + Card splitCard1 = CardRepository.instance.findCard("Armed // Dangerous").createCard(); + Card splitCard2 = CardRepository.instance.findCard("Alive // Well").createCard(); Assert.assertTrue(CardUtil.haveSameNames(splitCard1, "Armed", currentGame)); Assert.assertTrue(CardUtil.haveSameNames(splitCard1, "Dangerous", currentGame)); Assert.assertTrue(CardUtil.haveSameNames(splitCard1, "Armed // Dangerous", currentGame)); @@ -61,7 +61,7 @@ public class AliasesApiTest extends CardTestPlayerBase { Assert.assertFalse(CardUtil.haveSameNames(splitCard1, splitCard2)); // name with face down spells: face down spells don't have names, see https://github.com/magefree/mage/issues/6569 - Card bearCard = CardRepository.instance.findCard("Balduvian Bears").getCard(); + Card bearCard = CardRepository.instance.findCard("Balduvian Bears").createCard(); Spell normalSpell = new Spell(bearCard, bearCard.getSpellAbility(), playerA.getId(), Zone.HAND, currentGame); Spell faceDownSpell = new Spell(bearCard, bearCard.getSpellAbility(), playerA.getId(), Zone.HAND, currentGame); faceDownSpell.setFaceDown(true, currentGame); diff --git a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java index def70e36960..38c45bf3f5c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java @@ -16,7 +16,7 @@ public class ExtraTurnsTest extends CardTestPlayerBase { private void checkTurnControl(int turn, TestPlayer needTurnController, boolean isExtraTurn) { runCode("checking turn " + turn, turn, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { Player defaultTurnController = game.getPlayer(game.getActivePlayerId()); - Player realTurnController = defaultTurnController.getTurnControlledBy() == null ? defaultTurnController : game.getPlayer(defaultTurnController.getTurnControlledBy()); + Player realTurnController = game.getPlayer(defaultTurnController.getTurnControlledBy()); Assert.assertEquals(String.format("turn %d must be controlled by %s", turn, needTurnController.getName()), needTurnController.getName(), realTurnController.getName()); Assert.assertEquals(String.format("turn %d must be %s", turn, (isExtraTurn ? "extra turn" : "normal turn")), diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java index 92d2cb1c757..205132592df 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java @@ -3,12 +3,15 @@ package org.mage.test.utils; import mage.MageObject; import mage.abilities.keyword.FlyingAbility; import mage.constants.CommanderCardType; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.util.GameLog; import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestCommanderDuelBase; @@ -28,6 +31,11 @@ public class CardHintsTest extends CardTestCommanderDuelBase { // * client side: inject additional elements for popup support (e.g. "a" with "href") // * client side: process mouse move over a href and show object data like a card popup + private Document parseHtmlLog(String originalLog) { + // replace log's face down info by real empty name (need for compatibility) + return Jsoup.parse(originalLog.replace(EmptyNames.EMPTY_NAME_IN_LOGS, "")); + } + private void assertObjectHtmlLog(String originalLog, String needVisibleColorPart, String needVisibleNormalPart, String needId) { String needVisibleFull = needVisibleColorPart; if (!needVisibleNormalPart.isEmpty()) { @@ -43,7 +51,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase { Assert.assertTrue(mesPrefix + "can't find id" + mesPostfix, originalLog.contains(needId)); // html check - Element html = Jsoup.parse(originalLog); + Element html = parseHtmlLog(originalLog); Assert.assertEquals(mesPrefix + "can't find full text" + mesPostfix, needVisibleFull, html.text()); Element htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null); Assert.assertNotNull(mesPrefix + "can't find tag [font]" + mesPostfix, htmlFont); @@ -52,7 +60,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase { // improved log from client (with href and popup support) String popupLog = GameLog.injectPopupSupport(originalLog); - html = Jsoup.parse(popupLog); + html = parseHtmlLog(popupLog); Assert.assertEquals(mesPrefix + "injected, can't find full text" + mesPostfix, needVisibleFull, html.text()); // href Element htmlA = html.getElementsByTag("a").stream().findFirst().orElse(null); @@ -125,7 +133,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase { .stream() .map(c -> currentGame.getObject(c)) .collect(Collectors.toList())); - Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // defaul commander game already contains +1 commander + Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // default commander game already contains +1 commander sampleObjects.forEach(this::assertObjectSupport); } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java index 2f854c85e43..d162c5a9c90 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaUtilTest.java @@ -106,7 +106,7 @@ public class ManaUtilTest extends CardTestPlayerBase { */ private void testManaToPayVsLand(String manaToPay, String landName, int expected1, int expected2) { ManaCost unpaid = new ManaCostsImpl<>(manaToPay); - Card card = CardRepository.instance.findCard(landName).getCard(); + Card card = CardRepository.instance.findCard(landName).createCard(); Assert.assertNotNull(card); Map useableAbilities = getManaAbilities(card); @@ -134,7 +134,7 @@ public class ManaUtilTest extends CardTestPlayerBase { */ private void testManaToPayVsLand(String manaToPay, String landName, int expected1, Class expectedChosen) { ManaCost unpaid = new ManaCostsImpl<>(manaToPay); - Card card = CardRepository.instance.findCard(landName).getCard(); + Card card = CardRepository.instance.findCard(landName).createCard(); Assert.assertNotNull(card); Map useableAbilities = getManaAbilities(card); diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 8e3f79ab4bc..f1f210aa95c 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -38,6 +38,7 @@ import mage.game.draft.DraftCube; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.custom.CreatureToken; +import mage.game.permanent.token.custom.XmageToken; import mage.sets.TherosBeyondDeath; import mage.target.targetpointer.TargetPointer; import mage.util.CardUtil; @@ -96,7 +97,8 @@ public class VerifyCardDataTest { private static final List evergreenKeywords = Arrays.asList( "flying", "lifelink", "menace", "trample", "haste", "first strike", "hexproof", "fear", "deathtouch", "double strike", "indestructible", "reach", "flash", "defender", "vigilance", - "plainswalk", "islandwalk", "swampwalk", "mountainwalk", "forestwalk", "myriad", "prowess", "convoke" + "plainswalk", "islandwalk", "swampwalk", "mountainwalk", "forestwalk", "myriad", "prowess", "convoke", + "shroud", "banding", "flanking", "horsemanship", "legendary landwalk" ); private static final List doubleWords = new ArrayList<>(); @@ -268,8 +270,12 @@ public class VerifyCardDataTest { return skipListGet(listName).contains(set); } + /** + * For splitting printed rules text with multiple keywords on one line + */ private static boolean evergreenCheck(String s) { - return evergreenKeywords.contains(s) || s.startsWith("protection from") || s.startsWith("hexproof from") || s.startsWith("ward "); + return evergreenKeywords.contains(s) || s.startsWith("protection from") || s.startsWith("hexproof from") + || s.startsWith("ward ") || s.startsWith("rampage ") || s.startsWith("annihilator"); } private static boolean eqSet(Collection a, Collection b) { @@ -614,7 +620,7 @@ public class VerifyCardDataTest { CardInfo cardInfo = CardRepository.instance.findCardsByClass(info.getCardClass().getCanonicalName()).stream().findFirst().orElse(null); Assert.assertNotNull(cardInfo); - Card card = cardInfo.getCard(); + Card card = cardInfo.createCard(); Card secondCard = card.getSecondCardFace(); if (secondCard != null) { if (set.findCardInfoByClass(secondCard.getClass()).isEmpty()) { @@ -1315,7 +1321,7 @@ public class VerifyCardDataTest { // - fix error in token's constructor errorsList.add("Error: token must have default constructor with zero params: " + tokenClass.getName()); } else if (tokDataNamesIndex.getOrDefault(token.getName().replace(" Token", ""), "").isEmpty()) { - if (token instanceof CreatureToken) { + if (token instanceof CreatureToken || token instanceof XmageToken) { // ignore custom token builders continue; } @@ -1340,7 +1346,8 @@ public class VerifyCardDataTest { // CHECK: tokens must have Token word in the name if (token.getDescription().startsWith(token.getName() + ", ") || token.getDescription().contains("named " + token.getName()) - || (token instanceof CreatureToken)) { + || (token instanceof CreatureToken) + || (token instanceof XmageToken)) { // ignore some names: // - Boo, a legendary 1/1 red Hamster creature token with trample and haste // - 1/1 green Insect creature token with flying named Butterfly @@ -2302,11 +2309,11 @@ public class VerifyCardDataTest { private void checkWrongAbilitiesTextEnd() { // TODO: implement tests result/stats by github actions to show in check message compared to prev version - System.out.println(String.format("")); - System.out.println(String.format("Stats for %d cards checked for abilities text:", wrongAbilityStatsTotal)); - System.out.println(String.format(" - Cards with correct text: %5d (%.2f)", wrongAbilityStatsGood, wrongAbilityStatsGood * 100.0 / wrongAbilityStatsTotal)); - System.out.println(String.format(" - Cards with text errors: %5d (%.2f)", wrongAbilityStatsBad, wrongAbilityStatsBad * 100.0 / wrongAbilityStatsTotal)); - System.out.println(String.format("")); + System.out.println(); + System.out.printf("Stats for %d cards checked for abilities text:%n", wrongAbilityStatsTotal); + System.out.printf(" - Cards with correct text: %5d (%.2f)%n", wrongAbilityStatsGood, wrongAbilityStatsGood * 100.0 / wrongAbilityStatsTotal); + System.out.printf(" - Cards with text errors: %5d (%.2f)%n", wrongAbilityStatsBad, wrongAbilityStatsBad * 100.0 / wrongAbilityStatsTotal); + System.out.println(); } private void checkWrongAbilitiesText(Card card, MtgJsonCard ref, int cardIndex) { @@ -2378,15 +2385,11 @@ public class VerifyCardDataTest { if (ref.subtypes.contains("Adventure")) { for (int i = 0; i < refRules.length; i++) { - refRules[i] = new StringBuilder("Adventure ") - .append(ref.types.get(0)) - .append(" - ") - .append(ref.faceName) - .append(' ') - .append(ref.manaCost) - .append(" - ") - .append(refRules[i]) - .toString(); + refRules[i] = "Adventure " + + ref.types.get(0) + " - " + + ref.faceName + ' ' + + ref.manaCost + " - " + + refRules[i]; } } @@ -2723,7 +2726,7 @@ public class VerifyCardDataTest { if (!card.isBasic()) { fail(card, "supertype", "basic land must be SuperType.BASIC"); } - } else if (name.equals("Wastes")) { + } else if (name.equals("Wastes") || name.equals("Snow-Covered Wastes")) { // Wastes are SuperType.BASIC but not necessarily Rarity.LAND if (!card.isBasic()) { fail(card, "supertype", "Wastes must be SuperType.BASIC"); diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index baca1c2db65..63592d375c1 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -34,8 +34,10 @@ public enum MageIdentifier { SerraParagonWatcher, OneWithTheMultiverseWatcher("Without paying manacost"), JohannApprenticeSorcererWatcher, + AssembleThePlayersWatcher, KaghaShadowArchdruidWatcher, CourtOfLocthwainWatcher("Without paying manacost"), + LaraCroftTombRaiderWatcher, // ----------------------------// // alternate casts // @@ -68,7 +70,9 @@ public enum MageIdentifier { SqueeDubiousMonarchAlternateCast, WorldheartPhoenixAlternateCast, XandersPactAlternateCast, - TheTombOfAclazotzWatcher; + TheTombOfAclazotzWatcher, + + MeTheImmortalAlternateCast; /** * Additional text if there is need to differentiate two very similar effects diff --git a/Mage/src/main/java/mage/MageItem.java b/Mage/src/main/java/mage/MageItem.java index ff0692b34a8..ca8fc3400d8 100644 --- a/Mage/src/main/java/mage/MageItem.java +++ b/Mage/src/main/java/mage/MageItem.java @@ -1,16 +1,14 @@ - - package mage; import java.io.Serializable; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ @FunctionalInterface public interface MageItem extends Serializable { UUID getId(); + } diff --git a/Mage/src/main/java/mage/MageObject.java b/Mage/src/main/java/mage/MageObject.java index 0c8157995d1..4656944e022 100644 --- a/Mage/src/main/java/mage/MageObject.java +++ b/Mage/src/main/java/mage/MageObject.java @@ -34,6 +34,15 @@ public interface MageObject extends MageItem, Serializable, Copyable void setImageNumber(Integer imageNumber); + /** + * Get image file name + * - empty for default name from a card + * - non-empty for face down objects like Morph (GUI show empty name, but image must show some image) + */ + String getImageFileName(); + + void setImageFileName(String imageFile); + String getName(); /** diff --git a/Mage/src/main/java/mage/MageObjectImpl.java b/Mage/src/main/java/mage/MageObjectImpl.java index 3d2dfafdbd2..4528911e9d3 100644 --- a/Mage/src/main/java/mage/MageObjectImpl.java +++ b/Mage/src/main/java/mage/MageObjectImpl.java @@ -36,6 +36,7 @@ public abstract class MageObjectImpl implements MageObject { private String expansionSetCode = ""; private String cardNumber = ""; + private String imageFileName = ""; private int imageNumber = 0; protected List supertype = new ArrayList<>(); @@ -77,6 +78,7 @@ public abstract class MageObjectImpl implements MageObject { frameStyle = object.frameStyle; expansionSetCode = object.expansionSetCode; cardNumber = object.cardNumber; + imageFileName = object.imageFileName; imageNumber = object.imageNumber; power = object.power.copy(); toughness = object.toughness.copy(); @@ -266,6 +268,16 @@ public abstract class MageObjectImpl implements MageObject { this.cardNumber = cardNumber; } + @Override + public String getImageFileName() { + return imageFileName; + } + + @Override + public void setImageFileName(String imageFileName) { + this.imageFileName = imageFileName; + } + @Override public Integer getImageNumber() { return imageNumber; diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index d978ad7996e..1c285a8e6e2 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -35,15 +35,6 @@ import java.util.UUID; */ public interface Ability extends Controllable, Serializable { - /** - * Gets the globally unique id of the ability contained within the game. - * - * @return A {@link java.util.UUID} which the game will use to store and - * retrieve the exact instance of this ability. - */ - @Override - UUID getId(); - /** * Assigns a new {@link java.util.UUID} * @@ -71,14 +62,6 @@ public interface Ability extends Controllable, Serializable { */ AbilityType getAbilityType(); - /** - * Gets the id of the player in control of this ability. - * - * @return The {@link java.util.UUID} of the controlling player. - */ - @Override - UUID getControllerId(); - /** * Sets the id of the controller of this ability. * @@ -228,6 +211,8 @@ public interface Ability extends Controllable, Serializable { * Retrieves the {@link Target} located at the 0th index in the * {@link Targets}. A call to the method is equivalent to * {@link #getTargets()}.get(0).getFirstTarget(). + *

    + * Warning, if you effect uses target pointers then it must search getTargetPointer too * * @return The {@link java.util.UUID} of the first target within the targets * list. diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index d3032d33de4..0616397eb6d 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -428,6 +428,7 @@ public abstract class AbilityImpl implements Ability { case MORE_THAN_MEETS_THE_EYE: case BESTOW: case MORPH: + case DISGUISE: // from Snapcaster Mage: // If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs // (such as that of Foil). (2018-12-07) @@ -649,6 +650,11 @@ public abstract class AbilityImpl implements Ability { return controllerId; } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override public void setControllerId(UUID controllerId) { this.controllerId = controllerId; diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 7a0d8ca98e7..dd7ad11f98c 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -148,7 +148,7 @@ public class SpellAbility extends ActivatedAbilityImpl { if (!approvingObjects.isEmpty()) { Card card = game.getCard(sourceId); Zone zone = game.getState().getZone(sourceId); - if(card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) { + if (card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) { // Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse): approvingObjects.add(new ApprovingObject(this, game)); } @@ -160,8 +160,8 @@ public class SpellAbility extends ActivatedAbilityImpl { Player player = game.getPlayer(playerId); if (player != null && player.getCastSourceIdWithAlternateMana() - .getOrDefault(getSourceId(), Collections.emptySet()) - .contains(MageIdentifier.Default) + .getOrDefault(getSourceId(), Collections.emptySet()) + .contains(MageIdentifier.Default) ) { return ActivationStatus.getFalse(); } @@ -181,11 +181,10 @@ public class SpellAbility extends ActivatedAbilityImpl { } return ActivationStatus.getFalse(); } else { - if(canChooseTarget(game, playerId)) { - if(approvingObjects == null || approvingObjects.isEmpty()) { + if (canChooseTarget(game, playerId)) { + if (approvingObjects == null || approvingObjects.isEmpty()) { return ActivationStatus.withoutApprovingObject(true); - } - else { + } else { return new ActivationStatus(approvingObjects); } } @@ -308,22 +307,27 @@ public class SpellAbility extends ActivatedAbilityImpl { } /** - * Returns a card object with the spell characteristics like color, types, + * Returns combined card object with the spell characteristics like color, types, * subtypes etc. E.g. if you cast a Bestow card as enchantment, the * characteristics don't include the creature type. + *

    + * Warning, it's not a real card - use it as a blueprint or characteristics searching * - * @param game * @return card object with the spell characteristics */ public Card getCharacteristics(Game game) { Card spellCharacteristics = game.getSpell(this.getId()); if (spellCharacteristics == null) { + // playable check (without put to stack) spellCharacteristics = game.getCard(this.getSourceId()); } + if (spellCharacteristics != null) { if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) { - spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this); + // transform characteristics (morph, transform, bestow, etc) + spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this, game); } + spellCharacteristics = spellCharacteristics.copy(); } return spellCharacteristics; } diff --git a/Mage/src/main/java/mage/abilities/abilityword/CohortAbility.java b/Mage/src/main/java/mage/abilities/abilityword/CohortAbility.java new file mode 100644 index 00000000000..d7da3354aef --- /dev/null +++ b/Mage/src/main/java/mage/abilities/abilityword/CohortAbility.java @@ -0,0 +1,40 @@ +package mage.abilities.abilityword; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.Effect; +import mage.constants.AbilityWord; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetControlledPermanent; + +/** + * @author xenohedron + */ + +public class CohortAbility extends SimpleActivatedAbility { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "an untapped Ally you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + } + + public CohortAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, new TapSourceCost()); + this.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); + this.setAbilityWord(AbilityWord.COHORT); + } + + protected CohortAbility(final CohortAbility ability) { + super(ability); + } + + @Override + public CohortAbility copy() { + return new CohortAbility(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/AttackedByCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttackedByCreatureTriggeredAbility.java index ee247a19382..12ebb0e1ed0 100644 --- a/Mage/src/main/java/mage/abilities/common/AttackedByCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttackedByCreatureTriggeredAbility.java @@ -13,6 +13,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; /** * @author LevelX2 @@ -42,7 +43,7 @@ public class AttackedByCreatureTriggeredAbility extends TriggeredAbilityImpl { super(zone, effect, optional); this.setTargetPointer = setTargetPointer; this.filter = filter; - setTriggerPhrase("Whenever " + filter.getMessage() + " attacks you, "); + setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks you, "); } protected AttackedByCreatureTriggeredAbility(final AttackedByCreatureTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/AttacksWithCreaturesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksWithCreaturesTriggeredAbility.java index 8bdb2645a64..6fea411b8e4 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksWithCreaturesTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksWithCreaturesTriggeredAbility.java @@ -11,6 +11,7 @@ import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -91,7 +92,7 @@ public class AttacksWithCreaturesTriggeredAbility extends TriggeredAbilityImpl { } getEffects().setValue(VALUEKEY_NUMBER_ATTACKERS, attackers.size()); if (setTargetPointer) { - getEffects().setTargetPointer(new FixedTargets(attackers, game)); + getEffects().setTargetPointer(new FixedTargets(new ArrayList<>(attackers), game)); } return true; } diff --git a/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java index 682af94b300..4d4812e77d7 100644 --- a/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; @@ -14,20 +13,29 @@ import mage.game.events.GameEvent; public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends TriggeredAbilityImpl { private final CounterType counterType; + private final boolean onlyController; public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect) { this(counterType, effect, false); } public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect, boolean optional) { + this(counterType, effect, optional, false); + } + + public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect, boolean optional, boolean onlyController) { super(Zone.EXILED, effect, optional); this.counterType = counterType; - setTriggerPhrase("Whenever a " + counterType.getName() + " counter is removed from {this} while it's exiled, "); + this.onlyController = onlyController; + setTriggerPhrase("Whenever " + ( + onlyController ? ("you remove a " + counterType.getName() + " counter") : ("a " + counterType.getName() + " counter is removed") + ) + " from {this} while it's exiled, "); } private CounterRemovedFromSourceWhileExiledTriggeredAbility(final CounterRemovedFromSourceWhileExiledTriggeredAbility ability) { super(ability); this.counterType = ability.counterType; + this.onlyController = ability.onlyController; } @Override @@ -42,6 +50,8 @@ public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends Trigger @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getData().equals(counterType.getName()) && event.getTargetId().equals(this.getSourceId()); + return event.getData().equals(counterType.getName()) + && event.getTargetId().equals(this.getSourceId()) + && (!onlyController || event.getPlayerId().equals(getControllerId())); } } diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java index 94ad486bc8b..c01dee10210 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; @@ -12,6 +11,7 @@ import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; /** * @author LevelX2 @@ -43,7 +43,7 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp this.onlyCombat = onlyCombat; this.affectsDefendingPlayer = affectsDefendingPlayer; this.targetController = targetController; - setTriggerPhrase("Whenever " + filter.getMessage() + " deals " + (onlyCombat ? "combat " : "") + "damage to " + setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " deals " + (onlyCombat ? "combat " : "") + "damage to " + (targetController == TargetController.OPPONENT ? "an opponent" : "a player") + ", "); } diff --git a/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java index a4e79936fe9..42f5f4dae2d 100644 --- a/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java @@ -15,7 +15,11 @@ public class DrawCardOpponentTriggeredAbility extends TriggeredAbilityImpl { private final boolean setTargetPointer; public DrawCardOpponentTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) { - super(Zone.BATTLEFIELD, effect, optional); + this(Zone.BATTLEFIELD, effect, optional, setTargetPointer); + } + + public DrawCardOpponentTriggeredAbility(Zone zone, Effect effect, boolean optional, boolean setTargetPointer) { + super(zone, effect, optional); this.setTargetPointer = setTargetPointer; setTriggerPhrase("Whenever an opponent draws a card, "); } diff --git a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java index 1265b781146..4389e8b1519 100644 --- a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java @@ -6,15 +6,12 @@ import mage.abilities.effects.Effect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.constants.TargetController; -import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; import mage.util.CardUtil; -import mage.watchers.Watcher; - -import java.util.*; +import mage.watchers.common.CardsDrawnThisTurnWatcher; /** * @author TheElk801 @@ -37,7 +34,6 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { public DrawNthCardTriggeredAbility(Zone zone, Effect effect, boolean optional, TargetController targetController, int cardNumber) { super(zone, effect, optional); - this.addWatcher(new DrawCardWatcher()); this.targetController = targetController; this.cardNumber = cardNumber; this.addHint(hint); @@ -77,7 +73,8 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { default: throw new IllegalArgumentException("TargetController " + targetController + " not supported"); } - return DrawCardWatcher.checkEvent(event.getPlayerId(), event, game, cardNumber); + CardsDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsDrawnThisTurnWatcher.class); + return watcher != null && watcher.getCardsDrawnThisTurn(event.getPlayerId()) == cardNumber; } public String generateTriggerPhrase() { @@ -98,35 +95,3 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { return new DrawNthCardTriggeredAbility(this); } } - -class DrawCardWatcher extends Watcher { - - private final Map> drawMap = new HashMap<>(); - - DrawCardWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.DREW_CARD) { - return; - } - if (!drawMap.containsKey(event.getPlayerId())) { - drawMap.putIfAbsent(event.getPlayerId(), new ArrayList<>()); - } - drawMap.get(event.getPlayerId()).add(event.getId()); - } - - @Override - public void reset() { - super.reset(); - drawMap.clear(); - } - - static boolean checkEvent(UUID playerId, GameEvent event, Game game, int cardNumber) { - Map> drawMap = game.getState().getWatcher(DrawCardWatcher.class).drawMap; - return drawMap.containsKey(playerId) && Objects.equals(drawMap.get(playerId).size(), cardNumber) && event.getId().equals(drawMap.get(playerId).get(cardNumber - 1)); - } - -} diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreCountersAddedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreCountersAddedTriggeredAbility.java index 170ea810380..db1669cf0ef 100644 --- a/Mage/src/main/java/mage/abilities/common/OneOrMoreCountersAddedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreCountersAddedTriggeredAbility.java @@ -25,7 +25,7 @@ public class OneOrMoreCountersAddedTriggeredAbility extends TriggeredAbilityImpl setTriggerPhrase("Whenever one or more " + counterType.getName() + " counters are put on {this}, "); } - private OneOrMoreCountersAddedTriggeredAbility(final OneOrMoreCountersAddedTriggeredAbility ability) { + protected OneOrMoreCountersAddedTriggeredAbility(final OneOrMoreCountersAddedTriggeredAbility ability) { super(ability); this.counterType = ability.counterType; } diff --git a/Mage/src/main/java/mage/abilities/common/ScryTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ScryTriggeredAbility.java index aa6a5c8d12a..8490d441c5c 100644 --- a/Mage/src/main/java/mage/abilities/common/ScryTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ScryTriggeredAbility.java @@ -20,7 +20,7 @@ public class ScryTriggeredAbility extends TriggeredAbilityImpl { } public ScryTriggeredAbility(Zone zone, Effect effect, boolean optional) { - super(zone, effect, false); + super(zone, effect, optional); setTriggerPhrase("Whenever you scry, "); } diff --git a/Mage/src/main/java/mage/abilities/common/SkipExtraTurnsAbility.java b/Mage/src/main/java/mage/abilities/common/SkipExtraTurnsAbility.java new file mode 100644 index 00000000000..f10b273a002 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SkipExtraTurnsAbility.java @@ -0,0 +1,83 @@ +package mage.abilities.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * @author PurpleCrowbar + */ +public class SkipExtraTurnsAbility extends SimpleStaticAbility { + + private final boolean onlyOpponents; + + public SkipExtraTurnsAbility() { + this(false); + } + + public SkipExtraTurnsAbility(boolean onlyOpponents) { + super(Zone.BATTLEFIELD, new SkipExtraTurnsEffect(onlyOpponents)); + this.onlyOpponents = onlyOpponents; + } + + private SkipExtraTurnsAbility(final SkipExtraTurnsAbility ability) { + super(ability); + this.onlyOpponents = ability.onlyOpponents; + } + + @Override + public SkipExtraTurnsAbility copy() { + return new SkipExtraTurnsAbility(this); + } + + @Override + public String getRule() { + return "If a" + (onlyOpponents ? "n opponent" : " player") + " would begin an extra turn, that player skips that turn instead."; + } +} + +class SkipExtraTurnsEffect extends ReplacementEffectImpl { + + private final boolean onlyOpponents; + + SkipExtraTurnsEffect(boolean onlyOpponents) { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + this.onlyOpponents = onlyOpponents; + } + + private SkipExtraTurnsEffect(final SkipExtraTurnsEffect effect) { + super(effect); + this.onlyOpponents = effect.onlyOpponents; + } + + @Override + public SkipExtraTurnsEffect copy() { + return new SkipExtraTurnsEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + MageObject sourceObject = game.getObject(source); + if (player != null && sourceObject != null) { + game.informPlayers(sourceObject.getLogName() + ": Extra turn of " + player.getLogName() + " skipped"); + } + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.EXTRA_TURN; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return !onlyOpponents || game.getPlayer(source.getControllerId()).hasOpponent(event.getPlayerId(), game); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/SurveilTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SurveilTriggeredAbility.java new file mode 100644 index 00000000000..e4eee23d384 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SurveilTriggeredAbility.java @@ -0,0 +1,45 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author PurpleCrowbar + */ +public class SurveilTriggeredAbility extends TriggeredAbilityImpl { + + public SurveilTriggeredAbility(Effect effect) { + this(Zone.BATTLEFIELD, effect); + } + + public SurveilTriggeredAbility(Zone zone, Effect effect) { + super(zone, effect); + setTriggerPhrase("Whenever you surveil, " + (zone == Zone.GRAVEYARD ? "if {this} is in your graveyard, " : "")); + } + + private SurveilTriggeredAbility(final SurveilTriggeredAbility ability) { + super(ability); + } + + @Override + public SurveilTriggeredAbility copy() { + return new SurveilTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SURVEILED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (isControlledBy(event.getPlayerId())) { + this.getEffects().setValue("amount", event.getAmount()); + return true; + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/TurnFaceUpAbility.java b/Mage/src/main/java/mage/abilities/common/TurnFaceUpAbility.java index c49bcc857b2..d4975fb0d4f 100644 --- a/Mage/src/main/java/mage/abilities/common/TurnFaceUpAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TurnFaceUpAbility.java @@ -29,8 +29,8 @@ public class TurnFaceUpAbility extends SpecialAction { this.addCost(costs); this.usesStack = false; this.abilityType = AbilityType.SPECIAL_ACTION; - this.setRuleVisible(false); // will be made visible only to controller in CardView this.setWorksFaceDown(true); + this.setRuleVisible(false); // hide in face up, but show in face down view (it will be enabled as default ability) } protected TurnFaceUpAbility(final TurnFaceUpAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/AttackedPlayersPoisonedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/AttackedPlayersPoisonedCondition.java index f0659ff46fc..2ab5bbf468e 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/AttackedPlayersPoisonedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/AttackedPlayersPoisonedCondition.java @@ -1,11 +1,12 @@ - package mage.abilities.condition.common; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.counters.CounterType; import mage.game.Game; -import mage.watchers.common.PlayerAttackedStepWatcher; +import mage.game.combat.CombatGroup; + +import java.util.Objects; /** * A condition which checks whether any players being attacked are poisoned @@ -14,17 +15,23 @@ import mage.watchers.common.PlayerAttackedStepWatcher; * @author alexander-novo */ public enum AttackedPlayersPoisonedCondition implements Condition { - instance; @Override public boolean apply(Game game, Ability source) { - return game.getCombat().getDefenders().stream().map(defender -> game.getPlayer(defender)) - .anyMatch(player -> player != null && player.getCounters().containsKey(CounterType.POISON)); + return game.getCombat() + .getGroups() + .stream() + .map(CombatGroup::getDefenderId) + .filter(Objects::nonNull) + .distinct() + .map(game::getPlayer) + .filter(Objects::nonNull) + .anyMatch(player -> player.getCounters().containsKey(CounterType.POISON)); } @Override public String toString() { return "one or more players being attacked are poisoned"; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/SacrificedArtifactThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SacrificedArtifactThisTurnCondition.java index 298c158eff4..14af9225a6e 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/SacrificedArtifactThisTurnCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/SacrificedArtifactThisTurnCondition.java @@ -7,6 +7,10 @@ import mage.abilities.hint.Hint; import mage.game.Game; import mage.watchers.common.PermanentsSacrificedWatcher; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + /** * @author TheElk801 */ @@ -20,11 +24,12 @@ public enum SacrificedArtifactThisTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return game - .getState() - .getWatcher(PermanentsSacrificedWatcher.class) - .getThisTurnSacrificedPermanents(source.getControllerId()) - .stream() + return Optional.ofNullable(game + .getState() + .getWatcher(PermanentsSacrificedWatcher.class) + .getThisTurnSacrificedPermanents(source.getControllerId())) + .map(List::stream) + .orElseGet(Stream::empty) .anyMatch(permanent -> permanent.isArtifact(game)); } diff --git a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java index 83242ad81a5..363bf5c1605 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java @@ -14,6 +14,7 @@ import mage.game.events.GameEvent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; import java.awt.*; import java.util.Objects; @@ -25,16 +26,23 @@ import java.util.UUID; public class CollectEvidenceCost extends CostImpl { private final int amount; + private final boolean withSource; public CollectEvidenceCost(int amount) { + this(amount, false); + } + + public CollectEvidenceCost(int amount, boolean withSource) { super(); this.amount = amount; + this.withSource = withSource; this.text = "collect evidence " + amount; } private CollectEvidenceCost(final CollectEvidenceCost cost) { super(cost); this.amount = cost.amount; + this.withSource = cost.withSource; } @Override @@ -83,7 +91,11 @@ public class CollectEvidenceCost extends CostImpl { .mapToInt(MageObject::getManaValue) .sum() >= amount; if (paid) { - player.moveCards(cards, Zone.EXILED, source, game); + if (withSource) { + player.moveCardsToExile(cards.getCards(game), source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)); + } else { + player.moveCards(cards, Zone.EXILED, source, game); + } game.fireEvent(GameEvent.getEvent( GameEvent.EventType.EVIDENCE_COLLECTED, source.getSourceId(), source, source.getControllerId(), amount diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java index 8690b000414..0a518e03f9f 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java @@ -18,6 +18,7 @@ import mage.util.CardUtil; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; /** * @author nantuko @@ -95,7 +96,7 @@ public class ExileFromGraveCost extends CostImpl { CardUtil.getSourceName(game, source) ); if (setTargetPointer) { - source.getEffects().setTargetPointer(new FixedTargets(cardsToExile, game)); + source.getEffects().setTargetPointer(new FixedTargets(cardsToExile.getCards(game), game)); } paid = true; } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java index 19fb2515ba8..92b98edca22 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java @@ -50,10 +50,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl { public boolean apply(Game game, Ability source) { conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.apply(game, source); } if (!conditionState && effect.getDuration() == Duration.OneUse) { @@ -69,10 +69,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl { public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game, UUID playerId) { conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(sourceId, affectedAbility, source, game, playerId); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(sourceId, affectedAbility, source, game, playerId); } return false; @@ -82,10 +82,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl { public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(sourceId, source, affectedControllerId, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(sourceId, source, affectedControllerId, game); } return false; diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java index f4ded9a7ff3..5c147b67a22 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java @@ -81,10 +81,10 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -122,10 +122,10 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { } boolean conditionState = condition != null && condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.apply(game, source); } switch (effect.getDuration()) { diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousRuleModifyingEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousRuleModifyingEffect.java index 83d74dcd3c0..aa798646bb7 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousRuleModifyingEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousRuleModifyingEffect.java @@ -52,10 +52,10 @@ public class ConditionalContinuousRuleModifyingEffect extends ContinuousRuleModi } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -82,10 +82,10 @@ public class ConditionalContinuousRuleModifyingEffect extends ContinuousRuleModi init(source, game); } if (condition.apply(game, source)) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(event, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(event, source, game); } return false; diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java index 3adae692f9d..8c60a3f144d 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java @@ -51,10 +51,10 @@ public class ConditionalCostModificationEffect extends CostModificationEffectImp public boolean apply(Game game, Ability source, Ability abilityToModify) { conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source, abilityToModify); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.apply(game, source, abilityToModify); } if (!conditionState && effect.getDuration() == Duration.OneUse) { @@ -70,10 +70,10 @@ public class ConditionalCostModificationEffect extends CostModificationEffectImp public boolean applies(Ability abilityToModify, Ability source, Game game) { conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(abilityToModify, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(abilityToModify, source, game); } return false; diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java index ba2e32dd035..d07a6f08526 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java @@ -53,7 +53,7 @@ public class ConditionalOneShotEffect extends OneShotEffect { if (toApply.isEmpty()) { return true; } - toApply.setTargetPointer(this.targetPointer); + toApply.setTargetPointer(this.getTargetPointer().copy()); toApply.stream().forEach(effect -> effect.apply(game, source)); return true; } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java index 8600b50c7b6..911b06b2b97 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java @@ -68,10 +68,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl { } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -80,10 +80,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.replaceEvent(event, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.replaceEvent(event, source, game); } @@ -110,10 +110,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl { } conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(event, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(event, source, game); } return false; diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalReplacementEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalReplacementEffect.java index c57f64a637d..0060c66fa4a 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalReplacementEffect.java @@ -60,10 +60,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl { } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -72,10 +72,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.replaceEvent(event, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.replaceEvent(event, source, game); } if (!conditionState && effect.getDuration() == Duration.OneUse) { @@ -100,10 +100,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl { } conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(event, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(event, source, game); } return false; diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java index e8a94bb8d35..fc1d569d9d4 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java @@ -64,10 +64,10 @@ public class ConditionalRequirementEffect extends RequirementEffect { } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -80,10 +80,10 @@ public class ConditionalRequirementEffect extends RequirementEffect { } conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(permanent, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(permanent, source, game); } if (!conditionState && effect.getDuration() == Duration.OneUse) { diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java index 03da9b7c055..05ae2063883 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java @@ -63,10 +63,10 @@ public class ConditionalRestrictionEffect extends RestrictionEffect { } else { condition = baseCondition; } - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); effect.init(source, game); if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); otherwiseEffect.init(source, game); } initDone = true; @@ -79,10 +79,10 @@ public class ConditionalRestrictionEffect extends RestrictionEffect { } conditionState = condition.apply(game, source); if (conditionState) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.applies(permanent, source, game); } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); return otherwiseEffect.applies(permanent, source, game); } if (effect.getDuration() == Duration.OneUse) { diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/IntPlusDynamicValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/IntPlusDynamicValue.java index 27855bc14f5..3742630a3ec 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/IntPlusDynamicValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/IntPlusDynamicValue.java @@ -36,7 +36,7 @@ public class IntPlusDynamicValue implements DynamicValue { @Override public String toString() { - StringBuilder sb = new StringBuilder(baseValue); + StringBuilder sb = new StringBuilder(); sb.append(baseValue).append(" plus "); return sb.append(value.toString()).toString(); } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/TargetPermanentPowerCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/TargetPermanentPowerCount.java index ab7e13d4b97..3ae4fa76c9a 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/TargetPermanentPowerCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/TargetPermanentPowerCount.java @@ -4,7 +4,6 @@ package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; @@ -16,12 +15,9 @@ public enum TargetPermanentPowerCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - Permanent sourcePermanent = game.getPermanent(sourceAbility.getFirstTarget()); - if (sourcePermanent == null) { - sourcePermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getFirstTarget(), Zone.BATTLEFIELD); - } - if (sourcePermanent != null) { - return sourcePermanent.getPower().getValue(); + Permanent targetPermanent = effect.getTargetPointer().getFirstTargetPermanentOrLKI(game, sourceAbility); + if (targetPermanent != null) { + return targetPermanent.getPower().getValue(); } return 0; diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index 1fd59290f7b..c1456292c37 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -39,6 +39,11 @@ public interface ContinuousEffect extends Effect { boolean isInactive(Ability source, Game game); + /** + * Init ability data like ZCC or targets on first check in game cycle (ApplyEffects) + *

    + * Warning, if you setup target pointer in init then must call super.init at the end (after all choices) + */ void init(Ability source, Game game); void init(Ability source, Game game, UUID activePlayerId); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 015028a6ac2..ad8da740051 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -3,7 +3,6 @@ package mage.abilities.effects; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.CompoundAbility; -import mage.abilities.MageSingleton; import mage.abilities.keyword.ChangelingAbility; import mage.constants.*; import mage.filter.Filter; @@ -154,6 +153,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.discarded = true; } + @Override + public final void initNewTargetPointer() { + // continuous effect uses init code, so do nothing here + } + @Override public void init(Ability source, Game game) { init(source, game, game.getActivePlayerId()); @@ -161,7 +165,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public void init(Ability source, Game game, UUID activePlayerId) { - targetPointer.init(game, source); + getTargetPointer().init(game, source); // 20210112 - 611.2c // 611.2c If a continuous effect generated by the resolution of a spell or ability modifies the // characteristics or changes the controller of any objects, the set of objects it affects is diff --git a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java index b3abfdd1e2c..c5a81b5f93c 100644 --- a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java @@ -21,7 +21,8 @@ public abstract class EffectImpl implements Effect { protected EffectType effectType; // read related docs about static and dynamic targets in ContinuousEffectImpl.affectedObjectsSet - protected TargetPointer targetPointer = new FirstTargetPointer(); + // warning, do not change it directly, use setTargetPointer instead + private TargetPointer targetPointer = new FirstTargetPointer(); protected String staticText = ""; protected Map values; @@ -30,6 +31,8 @@ public abstract class EffectImpl implements Effect { public EffectImpl(Outcome outcome) { this.id = UUID.randomUUID(); this.outcome = outcome; + + initNewTargetPointer(); } protected EffectImpl(final EffectImpl effect) { @@ -48,6 +51,11 @@ public abstract class EffectImpl implements Effect { } } + /** + * Init target pointer by default (see TargetPointer for details) + */ + abstract public void initNewTargetPointer(); + @Override public UUID getId() { return id; @@ -81,7 +89,13 @@ public abstract class EffectImpl implements Effect { @Override public Effect setTargetPointer(TargetPointer targetPointer) { + if (targetPointer == null) { + // first target pointer is default + throw new IllegalArgumentException("Wrong code usage: target pointer can't be set to null: " + this); + } + this.targetPointer = targetPointer; + initNewTargetPointer(); return this; } diff --git a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java index 927c5941f14..96e29a2cef4 100644 --- a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java @@ -4,6 +4,7 @@ package mage.abilities.effects; import mage.constants.EffectType; import mage.constants.Outcome; +import mage.target.targetpointer.TargetPointer; /** * @author BetaSteward_at_googlemail.com @@ -15,6 +16,12 @@ public abstract class OneShotEffect extends EffectImpl { this.effectType = EffectType.ONESHOT; } + @Override + public final void initNewTargetPointer() { + // one short effects don't use init logic + this.getTargetPointer().setInitialized(); + } + protected OneShotEffect(final OneShotEffect effect) { super(effect); } @@ -24,4 +31,10 @@ public abstract class OneShotEffect extends EffectImpl { super.setText(staticText); return this; } + + @Override + public Effect setTargetPointer(TargetPointer targetPointer) { + super.setTargetPointer(targetPointer); + return this; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/BecomeBlockedTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/BecomeBlockedTargetEffect.java index fd2b8eea935..2da1039caeb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/BecomeBlockedTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/BecomeBlockedTargetEffect.java @@ -35,7 +35,7 @@ public class BecomeBlockedTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Set morSet = new HashSet<>(); - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetId); if (permanent == null) { continue; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ConjureCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ConjureCardEffect.java index 45848e36ac8..a21fecfd441 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ConjureCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ConjureCardEffect.java @@ -65,7 +65,7 @@ public class ConjureCardEffect extends OneShotEffect { } Set cards = new HashSet<>(); for (int i = 0; i < amount; i++) { - Card card = cardInfo.getCard(); + Card card = cardInfo.createCard(); cards.add(card); } game.loadCards(cards, source.getControllerId()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java index a85c4662277..85e533fdc90 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetSpellEffect.java @@ -79,12 +79,12 @@ public class CopyTargetSpellEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Spell spell; if (useLKI) { - spell = game.getSpellOrLKIStack(targetPointer.getFirst(game, source)); + spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source)); } else { - spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); } if (spell == null) { - spell = (Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK); + spell = (Spell) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK); } if (spell != null) { spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(), diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetStackAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetStackAbilityEffect.java index 737c49e6fd4..8c7f6d4a7f5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyTargetStackAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyTargetStackAbilityEffect.java @@ -22,7 +22,7 @@ public class CopyTargetStackAbilityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (stackAbility == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java index f45ac670e47..59fce1a993d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java @@ -60,7 +60,7 @@ public class CounterUnlessPaysEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); + StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); if (spell == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index 52426022ba9..a4c20b5618a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -49,7 +49,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { DelayedTriggeredAbility delayedAbility = ability.copy(); if (this.copyTargets) { if (source.getTargets().isEmpty()) { - delayedAbility.getEffects().setTargetPointer(targetPointer); + delayedAbility.getEffects().setTargetPointer(this.getTargetPointer().copy()); } else { delayedAbility.getTargets().addAll(source.getTargets()); for (Effect effect : delayedAbility.getEffects()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index 44c1530d9be..6e35a885845 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -25,6 +25,7 @@ import mage.util.functions.CopyTokenFunction; import mage.util.functions.EmptyCopyApplier; import java.util.*; +import java.util.stream.Collectors; /** * @author LevelX2 @@ -273,9 +274,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { Permanent tokenPermanent = game.getPermanent(tokenId); if (tokenPermanent != null) { addedTokenPermanents.add(tokenPermanent); - // add counters if necessary ie Ochre Jelly - if (counter != null - && numberOfCounters > 0) { + // TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786 + if (counter != null && numberOfCounters > 0) { tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game); } } @@ -418,7 +418,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { } else { effect = new SacrificeTargetEffect("sacrifice the token copies", source.getControllerId()); } - effect.setTargetPointer(new FixedTargets(addedTokenPermanents, game)); + effect.setTargetPointer(new FixedTargets(new ArrayList<>(addedTokenPermanents), game)); DelayedTriggeredAbility exileAbility; diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java index 96eb67cfb98..d15cec34ac5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -9,6 +8,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.constants.Zone; +import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; @@ -30,6 +30,8 @@ public class CreateTokenEffect extends OneShotEffect { private final boolean attacking; private String additionalRules; private List lastAddedTokenIds = new ArrayList<>(); + private CounterType counterType; + private DynamicValue numberOfCounters; public CreateTokenEffect(Token token) { this(token, StaticValue.get(1)); @@ -67,9 +69,17 @@ public class CreateTokenEffect extends OneShotEffect { this.tapped = effect.tapped; this.attacking = effect.attacking; this.lastAddedTokenIds.addAll(effect.lastAddedTokenIds); + this.counterType = effect.counterType; + this.numberOfCounters = effect.numberOfCounters; this.additionalRules = effect.additionalRules; } + public CreateTokenEffect entersWithCounters(CounterType counterType, DynamicValue numberOfCounters) { + this.counterType = counterType; + this.numberOfCounters = numberOfCounters; + return this; + } + @Override public CreateTokenEffect copy() { return new CreateTokenEffect(this); @@ -80,6 +90,15 @@ public class CreateTokenEffect extends OneShotEffect { int value = amount.calculate(game, source, this); token.putOntoBattlefield(value, game, source, source.getControllerId(), tapped, attacking); this.lastAddedTokenIds = token.getLastAddedTokenIds(); + // TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786 + if (counterType != null) { + for (UUID tokenId : lastAddedTokenIds) { + Permanent tokenPermanent = game.getPermanent(tokenId); + if (tokenPermanent != null) { + tokenPermanent.addCounters(counterType.createInstance(numberOfCounters.calculate(game, source, this)), source.getControllerId(), source, game); + } + } + } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenTargetEffect.java index 06a93cdc3a1..a1d2b36e89a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenTargetEffect.java @@ -63,7 +63,7 @@ public class CreateTokenTargetEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { int value = amount.calculate(game, source, this); if (value > 0) { - return token.putOntoBattlefield(value, game, source, targetPointer.getFirst(game, source), tapped, attacking, (UUID) getValue("playerToAttack")); + return token.putOntoBattlefield(value, game, source, getTargetPointer().getFirst(game, source), tapped, attacking, (UUID) getValue("playerToAttack")); } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageAllControlledTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageAllControlledTargetEffect.java index cdca2540918..daf9ad352c5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageAllControlledTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageAllControlledTargetEffect.java @@ -47,7 +47,7 @@ public class DamageAllControlledTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayerOrPlaneswalkerController(targetPointer.getFirst(game, source)); + Player player = game.getPlayerOrPlaneswalkerController(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java index 91646477f1f..84721c6224f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java @@ -41,7 +41,7 @@ public class DamageWithExcessEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); if (permanent == null || sourceObject == null) { return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DestroyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DestroyTargetEffect.java index cbd5b9bc1c8..df1944b5bce 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DestroyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DestroyTargetEffect.java @@ -51,7 +51,7 @@ public class DestroyTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null && permanent.isPhasedIn() diff --git a/Mage/src/main/java/mage/abilities/effects/common/DetainAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DetainAllEffect.java index 323e3577ca4..6f5127e1754 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DetainAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DetainAllEffect.java @@ -46,8 +46,7 @@ public class DetainAllEffect extends OneShotEffect { if (!game.isSimulation()) { game.informPlayers("Detained permanent: " + permanent.getName()); } - FixedTarget fixedTarget = new FixedTarget(permanent, game); - detainedObjects.add(fixedTarget); + detainedObjects.add(new FixedTarget(permanent, game)); } game.addEffect(new DetainAllRestrictionEffect(detainedObjects), source); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DetainTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DetainTargetEffect.java index ee63ca1eaf1..9d4eb2a4151 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DetainTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DetainTargetEffect.java @@ -108,7 +108,7 @@ class DetainRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfClashWonEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfClashWonEffect.java index 30c9d2e0c62..e7b80d9ab3f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfClashWonEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfClashWonEffect.java @@ -51,7 +51,7 @@ public class DoIfClashWonEffect extends OneShotEffect { executingEffect.setTargetPointer(new FixedTarget(opponent.getId())); } } else { - executingEffect.setTargetPointer(this.targetPointer); + executingEffect.setTargetPointer(this.getTargetPointer().copy()); } if (executingEffect instanceof OneShotEffect) { return executingEffect.apply(game, source); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java index fad28fa849b..c8c408787d3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -111,7 +111,7 @@ public class DoIfCostPaid extends OneShotEffect { private void applyEffects(Game game, Ability source, Effects effects) { if (!effects.isEmpty()) { for (Effect effect : effects) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { effect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessAnyPlayerPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessAnyPlayerPaysEffect.java index 5e6e1d8c25e..65c44b6e434 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessAnyPlayerPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessAnyPlayerPaysEffect.java @@ -104,7 +104,7 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect { // do the effects if nobody paid if (doEffect) { for (Effect effect : executingEffects) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { result &= effect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessControllerPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessControllerPaysEffect.java index 6614819785a..40f9f22a1cc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessControllerPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessControllerPaysEffect.java @@ -75,7 +75,7 @@ public class DoUnlessControllerPaysEffect extends OneShotEffect { // do the effects if not paid if (doEffect) { for (Effect effect : executingEffects) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { result &= effect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessTargetPlayerOrTargetsControllerPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessTargetPlayerOrTargetsControllerPaysEffect.java index 71d8c4ea4d9..f2aa6a26fa3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoUnlessTargetPlayerOrTargetsControllerPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoUnlessTargetPlayerOrTargetsControllerPaysEffect.java @@ -110,7 +110,7 @@ public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEf // do the effects if not paid if (doEffect) { for (Effect effect : executingEffects) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { result &= effect.apply(game, source); } else { @@ -118,7 +118,7 @@ public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEf } } } else if (otherwiseEffect != null) { - otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.setTargetPointer(this.getTargetPointer().copy()); if (otherwiseEffect instanceof OneShotEffect) { result &= otherwiseEffect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoWhenCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoWhenCostPaid.java index 5e59c33ad69..ae6a40caece 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoWhenCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoWhenCostPaid.java @@ -60,7 +60,7 @@ public class DoWhenCostPaid extends OneShotEffect { int bookmark = game.bookmarkState(); if (cost.pay(source, game, source, player.getId(), false)) { if (ability.getTargets().isEmpty()) { - ability.getEffects().setTargetPointer(getTargetPointer()); + ability.getEffects().setTargetPointer(this.getTargetPointer().copy()); } game.fireReflexiveTriggeredAbility(ability, source); player.resetStoredBookmark(game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java index e9c369720fa..ae482eb6919 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersUntapStepTargetEffect.java @@ -48,7 +48,7 @@ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleM if (game.getTurnStepType() != PhaseStep.UNTAP) { return false; } - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { if (!event.getTargetId().equals(targetId)) { continue; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoubleCountersTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DoubleCountersTargetEffect.java new file mode 100644 index 00000000000..bb34719a6bc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DoubleCountersTargetEffect.java @@ -0,0 +1,43 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author PurpleCrowbar + */ +public class DoubleCountersTargetEffect extends OneShotEffect { + + private final CounterType counterType; + + public DoubleCountersTargetEffect(CounterType counterType) { + super(Outcome.Benefit); + this.counterType = counterType; + staticText = "double the number of " + counterType.getName() + " counters on it"; + } + + private DoubleCountersTargetEffect(final DoubleCountersTargetEffect effect) { + super(effect); + this.counterType = effect.counterType; + } + + @Override + public DoubleCountersTargetEffect copy() { + return new DoubleCountersTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + return permanent.addCounters(counterType.createInstance( + permanent.getCounters(game).getCount(counterType) + ), source.getControllerId(), source, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/DraftFromSpellbookEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DraftFromSpellbookEffect.java index cc658c87f4c..062c110f18b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DraftFromSpellbookEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DraftFromSpellbookEffect.java @@ -70,7 +70,7 @@ public class DraftFromSpellbookEffect extends OneShotEffect { return false; } Set cards = new HashSet<>(); - cards.add(cardInfo.getCard()); + cards.add(cardInfo.createCard()); game.loadCards(cards, player.getId()); player.moveCards(cards, Zone.HAND, source, game); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileCardYouChooseTargetOpponentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileCardYouChooseTargetOpponentEffect.java index 1375fc13077..a6b0234d495 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileCardYouChooseTargetOpponentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileCardYouChooseTargetOpponentEffect.java @@ -32,7 +32,7 @@ public class ExileCardYouChooseTargetOpponentEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(targetPointer.getFirst(game, source)); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || opponent == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileCardsFromTopOfLibraryControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileCardsFromTopOfLibraryControllerEffect.java new file mode 100644 index 00000000000..62567f40912 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileCardsFromTopOfLibraryControllerEffect.java @@ -0,0 +1,100 @@ +package mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public class ExileCardsFromTopOfLibraryControllerEffect extends OneShotEffect { + + private final int amount; + private final boolean toUniqueExileZone; + private final boolean faceDown; + + public ExileCardsFromTopOfLibraryControllerEffect(int amount) { + this(amount, false); + } + + public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone) { + this(amount, toUniqueExileZone, false); + } + + public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone, boolean faceDown) { + this(amount, toUniqueExileZone, faceDown, false); + } + + /** + * @param amount number of cards to exile + * @param toUniqueExileZone moves the card to a source object dependant + * unique exile zone, so another effect of the same source object (e.g. + * Theater of Horrors) can identify the card + * @param faceDown if true, cards are exiled face down + * @param withFaceDownReminderText if true, add the reminder text for exiling one face down card + */ + public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone, boolean faceDown, boolean withFaceDownReminderText) { + super(Outcome.Exile); + this.amount = amount; + this.toUniqueExileZone = toUniqueExileZone; + this.faceDown = faceDown; + + staticText = "exile the top " + + ((amount > 1) ? CardUtil.numberToText(amount) + " cards" : "card") + + " of your library" + + (faceDown ? " face down" : "") + + (withFaceDownReminderText ? ". (You can't look at it.)" : ""); + } + + protected ExileCardsFromTopOfLibraryControllerEffect(final ExileCardsFromTopOfLibraryControllerEffect effect) { + super(effect); + this.amount = effect.amount; + this.toUniqueExileZone = effect.toUniqueExileZone; + this.faceDown = effect.faceDown; + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + UUID exileZoneId = null; + String exileZoneName = ""; + if (toUniqueExileZone) { + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject == null) { + return false; + } + exileZoneId = CardUtil.getExileZoneId(game, source); + exileZoneName = CardUtil.createObjectRealtedWindowTitle(source, game, null); + } + + Set cards = controller.getLibrary().getTopCards(game, amount); + if (cards.isEmpty()) { + return true; + } + + boolean exiledSuccessfully = false; + for (Card card : cards) { + card.setFaceDown(faceDown, game); + exiledSuccessfully |= controller.moveCardsToExile(card, source, game, !faceDown, exileZoneId, exileZoneName); + card.setFaceDown(faceDown, game); + } + return exiledSuccessfully; + } + + @Override + public ExileCardsFromTopOfLibraryControllerEffect copy() { + return new ExileCardsFromTopOfLibraryControllerEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileFromZoneTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileFromZoneTargetEffect.java index 5aeacfbb538..12d0457f6b0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileFromZoneTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileFromZoneTargetEffect.java @@ -52,7 +52,7 @@ public class ExileFromZoneTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player == null) { return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java index d1513b2090f..9ea93adf090 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java @@ -13,6 +13,7 @@ import mage.players.Player; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; @@ -74,7 +75,7 @@ public class ExileReturnBattlefieldNextEndStepTargetEffect extends OneShotEffect Effect effect = yourControl ? new ReturnToBattlefieldUnderYourControlTargetEffect(exiledOnly) : new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, exiledOnly); - effect.setTargetPointer(new FixedTargets(new CardsImpl(toExile), game)); + effect.setTargetPointer(new FixedTargets(toExile, game)); game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetCardCopyAndCastEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetCardCopyAndCastEffect.java index ba07134793b..344ac0a874b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetCardCopyAndCastEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetCardCopyAndCastEffect.java @@ -2,7 +2,6 @@ package mage.abilities.effects.common; import mage.ApprovingObject; import mage.abilities.Ability; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.constants.Outcome; @@ -52,7 +51,7 @@ public class ExileTargetCardCopyAndCastEffect extends OneShotEffect { player.moveCards(card, Zone.EXILED, source, game); Card cardCopy = game.copyCard(card, source, source.getControllerId()); if (optional && !player.chooseUse(outcome, "Cast copy of " + - card.getName() + " without paying its mana cost?", source, game)) { + card.getName() + (this.noMana ? " without paying its mana cost?" : "?" ), source, game)) { return true; } game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), Boolean.TRUE); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java index ef0ddbbe66c..8c404a46482 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java @@ -30,7 +30,7 @@ public class ExileTargetEffect extends OneShotEffect { private String exileZone = null; private UUID exileId = null; private boolean toSourceExileZone = false; // exile the targets to a source object specific exile zone (takes care of zone change counter) - private boolean withName = true; + private boolean withName = true; // for face down - allows to hide card name in game logs before real face down apply public ExileTargetEffect(String effectText) { this(); @@ -75,6 +75,7 @@ public class ExileTargetEffect extends OneShotEffect { return this; } + // TODO: replace to withHiddenName and instructions to use public void setWithName(boolean withName) { this.withName = withName; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java index 1b6b2c4da78..1b16ffdf372 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java @@ -55,14 +55,14 @@ public class ExileTargetForSourceEffect extends OneShotEffect { } Set objectsToMove = new LinkedHashSet<>(); - if (this.targetPointer instanceof FirstTargetPointer + if (this.getTargetPointer() instanceof FirstTargetPointer && source.getTargets().size() > 1) { for (Target target : source.getTargets()) { objectsToMove.addAll(target.getTargets()); } } else { - if (this.targetPointer != null && !this.targetPointer.getTargets(game, source).isEmpty()) { - objectsToMove.addAll(this.targetPointer.getTargets(game, source)); + if (!this.getTargetPointer().getTargets(game, source).isEmpty()) { + objectsToMove.addAll(this.getTargetPointer().getTargets(game, source)); } else { // issue with Madness keyword #6889 UUID fixedTargetId = null; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java index c94d2b5f89e..31f2aec2472 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java @@ -1,5 +1,6 @@ package mage.abilities.effects.common; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; @@ -14,7 +15,9 @@ import mage.players.Player; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; +import java.util.ArrayList; import java.util.Set; +import java.util.stream.Collectors; public class ExileTopXMayPlayUntilEffect extends OneShotEffect { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileUntilSourceLeavesEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileUntilSourceLeavesEffect.java index 95a3a8bbb8c..6d8d170ce33 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileUntilSourceLeavesEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileUntilSourceLeavesEffect.java @@ -56,9 +56,7 @@ public class ExileUntilSourceLeavesEffect extends OneShotEffect { } ExileTargetEffect effect = new ExileTargetEffect(CardUtil.getCardExileZoneId(game, source), permanent.getIdName()); - if (targetPointer != null) { // Grasping Giant - effect.setTargetPointer(targetPointer); - } + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect.apply(game, source)) { game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(returnToZone), source); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/FlipCoinEffect.java b/Mage/src/main/java/mage/abilities/effects/common/FlipCoinEffect.java index d289b93d621..377568d923c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/FlipCoinEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/FlipCoinEffect.java @@ -65,7 +65,7 @@ public class FlipCoinEffect extends OneShotEffect { } boolean result = true; for (Effect effect : controller.flipCoin(source, game, true) ? executingEffectsWon : executingEffectsLost) { - effect.setTargetPointer(this.targetPointer); + effect.setTargetPointer(this.getTargetPointer().copy()); if (effect instanceof OneShotEffect) { result &= effect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/GainLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/GainLifeTargetEffect.java index 761b0325869..4f1df0c4756 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/GainLifeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/GainLifeTargetEffect.java @@ -39,7 +39,7 @@ public class GainLifeTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID playerId : targetPointer.getTargets(game, source)) { + for (UUID playerId : getTargetPointer().getTargets(game, source)) { Player player = game.getPlayer(playerId); if (player != null) { player.gainLife(life.calculate(game, source, this), game, source); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ImprintTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ImprintTargetEffect.java index 3b9ad5cb2d0..c1fcefed258 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ImprintTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ImprintTargetEffect.java @@ -32,11 +32,11 @@ public class ImprintTargetEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent != null) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { sourcePermanent.imprint(permanent.getId(), game); } else { - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null) { sourcePermanent.imprint(card.getId(), game); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java index e3d095913dd..c708182bf6c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.abilities.effects.common; import mage.abilities.Ability; diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseControlOnOtherPlayersControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseControlOnOtherPlayersControllerEffect.java index 1e1b9bbc81e..691553c7f6a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseControlOnOtherPlayersControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseControlOnOtherPlayersControllerEffect.java @@ -9,6 +9,7 @@ import mage.game.Game; import mage.players.Player; /** + * TODO: delete, there are already end turn code with control reset * @author nantuko */ public class LoseControlOnOtherPlayersControllerEffect extends OneShotEffect { diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java index dfcb24b0f95..783bd56dfaa 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java @@ -28,7 +28,7 @@ public class LoseHalfLifeTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { Integer amount = (int) Math.ceil(player.getLife() / 2f); if (amount > 0) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetControllerEffect.java index eb1b093969b..9f8e9c09e99 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetControllerEffect.java @@ -42,11 +42,11 @@ public class LoseLifeTargetControllerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - MageObject targetCard = targetPointer.getFirstTargetPermanentOrLKI(game, source); + MageObject targetCard = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); // if target is a countered spell if (targetCard == null) { - targetCard = game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK); + targetCard = game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK); } if (targetCard != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetEffect.java index 1eaecdc0fc0..6f8453449ec 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeTargetEffect.java @@ -40,7 +40,7 @@ public class LoseLifeTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean applied = false; - for (UUID playerId : targetPointer.getTargets(game, source)) { + for (UUID playerId : getTargetPointer().getTargets(game, source)) { Player player = game.getPlayer(playerId); if (player != null && player.loseLife(amount.calculate(game, source, this), game, source, false) > 0) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayTapOrUntapTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayTapOrUntapTargetEffect.java index dc836388364..a463183e5b3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MayTapOrUntapTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MayTapOrUntapTargetEffect.java @@ -23,7 +23,7 @@ public class MayTapOrUntapTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); Player player = game.getPlayer(source.getControllerId()); if (target != null && player != null) { if (target.isTapped()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java index e0bfbafdf1c..eaabf2f5e90 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java @@ -105,7 +105,7 @@ public class MeldEffect extends OneShotEffect { if (cardInfoList.isEmpty()) { return false; } - MeldCard meldCard = (MeldCard) cardInfoList.get(0).getCard().copy(); + MeldCard meldCard = (MeldCard) cardInfoList.get(0).createCard().copy(); meldCard.setOwnerId(controller.getId()); meldCard.setTopHalfCard(meldWithCard, game); meldCard.setBottomHalfCard(sourceCard, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java index 05b76e42f5f..bd5fa91f6fa 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java @@ -38,7 +38,7 @@ public class MillCardsTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.millCards(numberCards.calculate(game, source, this), source, game); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageFromChosenSourceToYouEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageFromChosenSourceToYouEffect.java index e8ae3485aff..967cb6ccc63 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageFromChosenSourceToYouEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageFromChosenSourceToYouEffect.java @@ -42,6 +42,7 @@ public class PreventAllDamageFromChosenSourceToYouEffect extends PreventionEffec @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); // be sure to note the target source's zcc, etc, if able. if (targetSource.getFirstTarget() != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageBySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageBySourceEffect.java index a2faf91e017..2f254477516 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageBySourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageBySourceEffect.java @@ -47,6 +47,7 @@ public class PreventDamageBySourceEffect extends PreventionEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); mageObjectReference = new MageObjectReference(target.getFirstTarget(), game); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetEffect.java index 998781b62b7..ae5e8c9b468 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetEffect.java @@ -45,7 +45,7 @@ public class PreventDamageToTargetEffect extends PreventionEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return !this.used && super.applies(event, source, game) && event.getTargetId().equals(targetPointer.getFirst(game, source)); + return !this.used && super.applies(event, source, game) && event.getTargetId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToTargetEffect.java index 789a675fbc4..7b5c872dc7c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToTargetEffect.java @@ -43,6 +43,7 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } @@ -56,7 +57,7 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE @Override public boolean applies(GameEvent event, Ability source, Game game) { if (!this.used && super.applies(event, source, game)) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) { + if (event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) { return true; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToYouEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToYouEffect.java index b8468ab444a..03ff1e1396a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToYouEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventNextDamageFromChosenSourceToYouEffect.java @@ -43,6 +43,7 @@ public class PreventNextDamageFromChosenSourceToYouEffect extends PreventionEffe @Override public void init(Ability source, Game game) { + super.init(source, game); this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java index e35e3813ccf..c980b60f011 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java @@ -54,7 +54,7 @@ public class PutOnLibraryTargetEffect extends OneShotEffect { if (controller != null) { List cards = new ArrayList<>(); List permanents = new ArrayList<>(); - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { switch (game.getState().getZone(targetId)) { case BATTLEFIELD: Permanent permanent = game.getPermanent(targetId); diff --git a/Mage/src/main/java/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java index 4df947e8761..c9feeccd4fc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java @@ -1,6 +1,7 @@ package mage.abilities.effects.common; import mage.abilities.Ability; +import mage.abilities.Mode; import mage.abilities.effects.RedirectionEffect; import mage.constants.Duration; import mage.game.Game; @@ -14,7 +15,6 @@ public class RedirectDamageFromSourceToTargetEffect extends RedirectionEffect { public RedirectDamageFromSourceToTargetEffect(Duration duration, int amountToRedirect, UsageType usageType) { super(duration, amountToRedirect, usageType); - staticText = "The next " + amountToRedirect + " damage that would be dealt to {this} this turn is dealt to target creature you control instead."; } protected RedirectDamageFromSourceToTargetEffect(final RedirectDamageFromSourceToTargetEffect effect) { @@ -39,4 +39,14 @@ public class RedirectDamageFromSourceToTargetEffect extends RedirectionEffect { } return false; } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return "the next " + amountToRedirect + " damage that would be dealt to {this} this turn is dealt to " + + getTargetPointer().describeTargets(mode.getTargets(), "that creature") + + " instead"; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/RegenerateSourceWithReflexiveEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RegenerateSourceWithReflexiveEffect.java index 47e66554ccf..a100eb4c23b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RegenerateSourceWithReflexiveEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RegenerateSourceWithReflexiveEffect.java @@ -39,7 +39,7 @@ public class RegenerateSourceWithReflexiveEffect extends RegenerateSourceEffect if (super.replaceEvent(event, source, game)) { if (this.setReflexiveTarget) { reflexive.getEffects().setTargetPointer( - new FixedTarget(targetPointer.getFirst(game, source), game) + new FixedTarget(getTargetPointer().getFirst(game, source), game) ); } game.fireReflexiveTriggeredAbility(reflexive, source); diff --git a/Mage/src/main/java/mage/abilities/effects/common/RegenerateTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RegenerateTargetEffect.java index 5447636ce2f..7a312b95615 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RegenerateTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RegenerateTargetEffect.java @@ -26,7 +26,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); - RegenerateSourceEffect.initRegenerationShieldInfo(game, source, targetPointer.getFirst(game, source)); + RegenerateSourceEffect.initRegenerationShieldInfo(game, source, getTargetPointer().getFirst(game, source)); } @Override @@ -37,7 +37,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { //20110204 - 701.11 - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null && permanent.regenerate(source, game)) { this.used = true; return true; @@ -53,7 +53,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { //20110204 - 701.11c - event.getAmount() is used to signal if regeneration is allowed - return event.getAmount() == 0 && event.getTargetId().equals(targetPointer.getFirst(game, source)) && !this.used; + return event.getAmount() == 0 && event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && !this.used; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java index c0771ccab9e..0bcb084d1a0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java @@ -22,6 +22,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { private final Zone returnToZone; private boolean pluralCards; private boolean pluralOwners; + private boolean putPhrasing; /** * @param zone Zone the card should return to @@ -31,6 +32,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { this.returnToZone = zone; this.pluralCards = false; this.pluralOwners = false; + this.putPhrasing = false; updateText(); } @@ -39,6 +41,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { this.returnToZone = effect.returnToZone; this.pluralCards = effect.pluralCards; this.pluralOwners = effect.pluralOwners; + this.putPhrasing = effect.putPhrasing; } @Override @@ -77,16 +80,22 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { return true; } - public ReturnFromExileForSourceEffect withText(boolean pluralCards, boolean pluralOwners) { + public ReturnFromExileForSourceEffect withText(boolean pluralCards, boolean pluralOwners, boolean putPhrasing) { this.pluralCards = pluralCards; this.pluralOwners = pluralOwners; + this.putPhrasing = putPhrasing; updateText(); return this; } private void updateText() { StringBuilder sb = new StringBuilder(); - sb.append("return the exiled ").append(pluralCards ? "cards" : "card").append(" to "); + if (putPhrasing) { + sb.append("put ").append(pluralCards ? "all cards " : "the card ").append("exiled with {this} "); + sb.append(returnToZone == Zone.BATTLEFIELD ? "onto " : "into "); + } else { + sb.append("return the exiled ").append(pluralCards ? "cards" : "card").append(" to "); + } if (returnToZone == Zone.BATTLEFIELD) { sb.append("the battlefield under "); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java index 9bb538495ea..4323e3b2115 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java @@ -23,9 +23,8 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect private final boolean tapped; private final boolean attacking; - // If true, creatures are returned to their owner's control. - // If false, creatures are returned under the effect's controller control. - private final boolean underOwnerControl; + + // Targets are returned under the control of the effect controller (e.g. "under your control") public ReturnFromGraveyardToBattlefieldTargetEffect() { this(false); @@ -34,22 +33,17 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped) { this(tapped, false); } - public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking) { - this(tapped, attacking, false); - } - public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking, boolean underOwnerControl) { + public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking) { super(Outcome.PutCreatureInPlay); this.tapped = tapped; this.attacking = attacking; - this.underOwnerControl = underOwnerControl; } protected ReturnFromGraveyardToBattlefieldTargetEffect(final ReturnFromGraveyardToBattlefieldTargetEffect effect) { super(effect); this.tapped = effect.tapped; this.attacking = effect.attacking; - this.underOwnerControl = effect.underOwnerControl; } @Override @@ -68,7 +62,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect cardsToMove.add(card); } } - controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, tapped, false, underOwnerControl, null); + controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, tapped, false, false, null); if (attacking) { for (Card card : cardsToMove) { game.getCombat().addAttackingCreature(card.getId(), game); @@ -119,12 +113,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect sb.append(" attacking"); } if (!yourGrave) { - if (underOwnerControl) { - sb.append("under their owner's control"); - } - else { - sb.append(" under your control"); - } + sb.append(" under your control"); } return sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java index 8dd57eeb247..545804d5935 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java @@ -2,15 +2,9 @@ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.constants.Duration; -import mage.constants.Outcome; import mage.counters.Counter; +import mage.counters.Counters; import mage.game.Game; -import mage.game.events.EntersTheBattlefieldEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -19,27 +13,24 @@ import java.util.UUID; */ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends ReturnFromGraveyardToBattlefieldTargetEffect { - private final Counter counter; - private final boolean additional; + private final Counters counters; + private final String counterText; public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter) { this(counter, false); } public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter, boolean additional) { - this(counter, additional, false); - } - - public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter, boolean additional, boolean underOwnerControl) { - super(false, false, underOwnerControl); - this.counter = counter; - this.additional = additional; + super(false); + this.counters = new Counters(); + this.counters.addCounter(counter); + this.counterText = makeText(counter, additional); } protected ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(final ReturnFromGraveyardToBattlefieldWithCounterTargetEffect effect) { super(effect); - this.counter = effect.counter.copy(); - this.additional = effect.additional; + this.counters = effect.counters.copy(); + this.counterText = effect.counterText; } @Override @@ -50,20 +41,13 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret @Override public boolean apply(Game game, Ability source) { for (UUID targetId : getTargetPointer().getTargets(game, source)) { - AddCounterTargetReplacementEffect counterEffect = new AddCounterTargetReplacementEffect(counter); - counterEffect.setTargetPointer(new FixedTarget(targetId, game)); - game.addEffect(counterEffect, source); + game.setEnterWithCounters(targetId, counters.copy()); } return super.apply(game, source); } - @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } - StringBuilder sb = new StringBuilder(super.getText(mode)); - sb.append(" with "); + private String makeText(Counter counter, boolean additional) { + StringBuilder sb = new StringBuilder(" with "); if (additional) { if (counter.getCount() == 1) { sb.append("an"); @@ -82,52 +66,14 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret if (counter.getCount() != 1) { sb.append('s'); } - if (targetPointer.isPlural(mode.getTargets())) { - sb.append(" on them"); - } else { - sb.append(" on it"); - } return sb.toString(); } -} - -class AddCounterTargetReplacementEffect extends ReplacementEffectImpl { - - private final Counter counter; - - public AddCounterTargetReplacementEffect(Counter counter) { - super(Duration.EndOfStep, Outcome.BoostCreature); - this.counter = counter; - } - - private AddCounterTargetReplacementEffect(final AddCounterTargetReplacementEffect effect) { - super(effect); - this.counter = effect.counter.copy(); - } @Override - public AddCounterTargetReplacementEffect copy() { - return new AddCounterTargetReplacementEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return getTargetPointer().getTargets(game, source).contains(event.getTargetId()); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget(); - if (creature == null) { - return false; + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; } - creature.addCounters(counter.copy(), source.getControllerId(), source, game, event.getAppliedEffects()); - discard(); - return false; + return super.getText(mode) + counterText + (getTargetPointer().isPlural(mode.getTargets()) ? " on them" : " on it"); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java index 1f7e6ae523e..5ded6ad025f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java @@ -27,7 +27,7 @@ public class ReturnToHandChosenControlledPermanentEffect extends ReturnToHandCho @Override public boolean apply(Game game, Ability source) { - this.targetPointer = new FixedTarget(source.getControllerId()); + this.setTargetPointer(new FixedTarget(source.getControllerId())); return super.apply(game, source); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java index a6cd2c97f41..a62e80c86d4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java @@ -39,7 +39,7 @@ public class ReturnToHandTargetEffect extends OneShotEffect { } List copyIds = new ArrayList<>(); Set cards = new LinkedHashSet<>(); - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { MageObject mageObject = game.getObject(targetId); if (mageObject != null) { if (mageObject instanceof Spell diff --git a/Mage/src/main/java/mage/abilities/effects/common/RevealAndSeparatePilesEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RevealAndSeparatePilesEffect.java index 4cb97992c5b..2398c886a94 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RevealAndSeparatePilesEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RevealAndSeparatePilesEffect.java @@ -107,7 +107,10 @@ public class RevealAndSeparatePilesEffect extends OneShotEffect { Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, toReveal)); controller.revealCards(source, cards, game); - Player separatingPlayer = this.getExecutingPlayer(controller, game, source, playerWhoSeparates, "separate the revealed cards"); + Player separatingPlayer = getExecutingPlayer(controller, game, source, playerWhoSeparates, "separate the revealed cards"); + if (separatingPlayer == null) { + return false; + } TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, filter); List pile1 = new ArrayList<>(); separatingPlayer.choose(Outcome.Neutral, cards, target, source, game); @@ -117,10 +120,12 @@ public class RevealAndSeparatePilesEffect extends OneShotEffect { .filter(Objects::nonNull) .forEach(pile1::add); cards.removeIf(target.getTargets()::contains); - List pile2 = new ArrayList<>(); - pile2.addAll(cards.getCards(game)); + List pile2 = new ArrayList<>(cards.getCards(game)); - Player choosingPlayer = this.getExecutingPlayer(controller, game, source, playerWhoChooses, "choose the piles"); + Player choosingPlayer = getExecutingPlayer(controller, game, source, playerWhoChooses, "choose the piles"); + if (choosingPlayer == null) { + return false; + } boolean choice = choosingPlayer.choosePile(outcome, "Choose a pile to put into hand.", pile1, pile2, game); Zone pile1Zone = choice ? Zone.HAND : targetZone; diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeControllerEffect.java index 7f10dc889ab..3a475337074 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeControllerEffect.java @@ -31,7 +31,7 @@ public class SacrificeControllerEffect extends SacrificeEffect { @Override public boolean apply(Game game, Ability source) { - this.targetPointer = new FixedTarget(source.getControllerId()); + this.setTargetPointer(new FixedTarget(source.getControllerId())); return super.apply(game, source); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java index 8da2ede2e05..2a0367c9edf 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java @@ -51,7 +51,7 @@ public class SacrificeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { boolean applied = false; - for (UUID playerId : targetPointer.getTargets(game, source)) { + for (UUID playerId : getTargetPointer().getTargets(game, source)) { Player player = game.getPlayer(playerId); if (player == null) { continue; diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeTargetEffect.java index 27d0fb21cc1..505e00898e7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeTargetEffect.java @@ -49,7 +49,7 @@ public class SacrificeTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null && (playerIdThatHasToSacrifice == null || playerIdThatHasToSacrifice.equals(permanent.getControllerId()))) { permanent.sacrifice(source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/SetPlayerLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SetPlayerLifeTargetEffect.java index fa7165026a4..4f758ff9eed 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SetPlayerLifeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SetPlayerLifeTargetEffect.java @@ -38,7 +38,7 @@ public class SetPlayerLifeTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.setLife(amount.calculate(game, source, this), game, source); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ShuffleLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ShuffleLibraryTargetEffect.java index 8dbd7e6bfb6..e326952f49f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ShuffleLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ShuffleLibraryTargetEffect.java @@ -29,7 +29,7 @@ public class ShuffleLibraryTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.shuffleLibrary(source, game); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/SkipCombatStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SkipCombatStepEffect.java index f27cb50fe3c..cd221d17ff6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SkipCombatStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SkipCombatStepEffect.java @@ -39,6 +39,6 @@ public class SkipCombatStepEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getPlayerId().equals(targetPointer.getFirst(game, source)); + return event.getPlayerId().equals(getTargetPointer().getFirst(game, source)); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SkipDrawStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SkipDrawStepEffect.java index 7dda045730e..4a4e2beb262 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SkipDrawStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SkipDrawStepEffect.java @@ -1,33 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ - package mage.abilities.effects.common; import mage.abilities.Ability; diff --git a/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java index b5c279e4a7c..326708f12b0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java @@ -31,13 +31,11 @@ public class SkipNextPlayerUntapStepEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = null; - if (targetPointer != null) { - if (!this.targetPointer.getTargets(game, source).isEmpty()) { - player = game.getPlayer(targetPointer.getFirst(game, source)); - } else { - player = game.getPlayer(source.getControllerId()); - } + Player player; + if (!this.getTargetPointer().getTargets(game, source).isEmpty()) { + player = game.getPlayer(getTargetPointer().getFirst(game, source)); + } else { + player = game.getPlayer(source.getControllerId()); } if (player != null) { game.getState().getTurnMods().add(new TurnMod(player.getId()).withSkipStep(PhaseStep.UNTAP)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/SkipUntapStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SkipUntapStepEffect.java index 444860dbff7..be7504b293b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SkipUntapStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SkipUntapStepEffect.java @@ -1,33 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ - package mage.abilities.effects.common; import mage.abilities.Ability; diff --git a/Mage/src/main/java/mage/abilities/effects/common/TapAllTargetPlayerControlsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TapAllTargetPlayerControlsEffect.java index 44baac8ce81..95f8f34908b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/TapAllTargetPlayerControlsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/TapAllTargetPlayerControlsEffect.java @@ -30,7 +30,7 @@ public class TapAllTargetPlayerControlsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { List permanents = game.getBattlefield().getAllActivePermanents(filter, player.getId(), game); for (Permanent p : permanents) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/TapTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TapTargetEffect.java index 2d3aab251f6..329ce20cf63 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/TapTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/TapTargetEffect.java @@ -38,7 +38,7 @@ public class TapTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID target : targetPointer.getTargets(game, source)) { + for (UUID target : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(target); if (permanent != null) { permanent.tap(source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlSourceEffect.java new file mode 100644 index 00000000000..dfa755a0fea --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlSourceEffect.java @@ -0,0 +1,61 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * @author xenohedron + */ +public class TargetPlayerGainControlSourceEffect extends OneShotEffect { + + private final String playerDescription; + + public TargetPlayerGainControlSourceEffect() { + this(""); + } + + public TargetPlayerGainControlSourceEffect(String playerDescription) { + super(Outcome.Benefit); + this.playerDescription = playerDescription; + } + + protected TargetPlayerGainControlSourceEffect(final TargetPlayerGainControlSourceEffect effect) { + super(effect); + this.playerDescription = effect.playerDescription; + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, player.getId() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } + + @Override + public TargetPlayerGainControlSourceEffect copy() { + return new TargetPlayerGainControlSourceEffect(this); + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return (playerDescription.isEmpty() ? getTargetPointer().describeTargets(mode.getTargets(), "that player") : playerDescription) + + " gains control of {this}"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlTargetPermanentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlTargetPermanentEffect.java new file mode 100644 index 00000000000..87936456eaa --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerGainControlTargetPermanentEffect.java @@ -0,0 +1,67 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * @author xenohedron + */ +public class TargetPlayerGainControlTargetPermanentEffect extends OneShotEffect { + + private final String playerDescription; + + public TargetPlayerGainControlTargetPermanentEffect() { + this(""); + } + + public TargetPlayerGainControlTargetPermanentEffect(String playerDescription) { + super(Outcome.Benefit); + this.playerDescription = playerDescription; + } + + protected TargetPlayerGainControlTargetPermanentEffect(final TargetPlayerGainControlTargetPermanentEffect effect) { + super(effect); + this.playerDescription = effect.playerDescription; + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getTargets().size() != 2) { + throw new IllegalStateException("It must have two targets, but found " + source.getTargets().size()); + } + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Permanent permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (player == null || permanent == null) { + return false; + } + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, player.getId() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } + + @Override + public TargetPlayerGainControlTargetPermanentEffect copy() { + return new TargetPlayerGainControlTargetPermanentEffect(this); + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + if (mode.getTargets().size() != 2) { + throw new IllegalStateException("It must have two targets, but found " + mode.getTargets().size()); + } + return (playerDescription.isEmpty() ? mode.getTargets().get(0).getDescription() : playerDescription) + + " gains control of " + mode.getTargets().get(1).getDescription(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/UntapTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/UntapTargetEffect.java index 7487d2b8075..c979835355f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/UntapTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/UntapTargetEffect.java @@ -36,7 +36,7 @@ public class UntapTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID target : targetPointer.getTargets(game, source)) { + for (UUID target : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(target); if (permanent != null) { permanent.untap(game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java index 81b044ef6aa..61b88863b46 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java @@ -13,10 +13,7 @@ import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockTargetEffect.java index 9b159d11aa4..79ac4326648 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockTargetEffect.java @@ -22,7 +22,7 @@ public class CantAttackBlockTargetEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getId().equals(targetPointer.getFirst(game, source)); + return permanent.getId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackTargetEffect.java index 39e3a0caad5..213d73fce34 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackTargetEffect.java @@ -22,7 +22,7 @@ public class CantAttackTargetEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getId().equals(targetPointer.getFirst(game, source)); + return permanent.getId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java index 84c87acae1d..36e9b7ef3c4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java @@ -31,9 +31,9 @@ public class CantAttackYouAllEffect extends RestrictionEffect { super(duration, Outcome.Benefit); this.filterAttacker = filter; this.alsoPlaneswalker = alsoPlaneswalker; - staticText = filterAttacker.getMessage() + " can't attack you" - + (alsoPlaneswalker ? " or planeswalkers you control" : "") - + (duration == Duration.UntilYourNextTurn || duration == Duration.UntilEndOfYourNextTurn ? " " + duration.toString() : ""); + staticText = (duration == Duration.UntilYourNextTurn ? duration.toString() + ", " : "") + + filterAttacker.getMessage() + " can't attack you" + + (alsoPlaneswalker ? " or planeswalkers you control" : ""); } protected CantAttackYouAllEffect(final CantAttackYouAllEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockAttachedEffect.java index 54c6978d55a..35b21acf0ba 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockAttachedEffect.java @@ -60,7 +60,7 @@ public class CantBlockAttachedEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { if (affectedObjectsSet) { - return targetPointer.getFirst(game, source).equals(permanent.getId()); + return getTargetPointer().getFirst(game, source).equals(permanent.getId()); } return permanent.getAttachments().contains(source.getSourceId()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockTargetEffect.java index 2a7f97ec79c..ef10bac67d9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantBlockTargetEffect.java @@ -22,7 +22,7 @@ public class CantBlockTargetEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return this.targetPointer.getTargets(game, source).contains(permanent.getId()); + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubTypeTargetEffect.java index 67c77a0e3ec..40a69a65e9e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubTypeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubTypeTargetEffect.java @@ -28,7 +28,7 @@ public class AddCardSubTypeTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source)); if (target != null) { target.addSubType(game, addedSubType); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java index d695c1e8974..7cda13f3c0e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardTypeTargetEffect.java @@ -40,7 +40,7 @@ public class AddCardTypeTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { boolean result = false; - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent target = game.getPermanent(targetId); if (target != null) { for (CardType cardType : addedCardTypes) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AssignNoCombatDamageTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AssignNoCombatDamageTargetEffect.java index 4cc9a832b1f..8c69b3025ae 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AssignNoCombatDamageTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AssignNoCombatDamageTargetEffect.java @@ -50,6 +50,6 @@ public class AssignNoCombatDamageTargetEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return ((DamageEvent) event).isCombatDamage() && event.getSourceId().equals(targetPointer.getFirst(game, source)); + return ((DamageEvent) event).isCombatDamage() && event.getSourceId().equals(getTargetPointer().getFirst(game, source)); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandTargetEffect.java index 45bcf6ed829..fde7e556c32 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandTargetEffect.java @@ -97,7 +97,7 @@ public class BecomesBasicLandTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (UUID targetPermanent : targetPointer.getTargets(game, source)) { + for (UUID targetPermanent : getTargetPointer().getTargets(game, source)) { Permanent land = game.getPermanent(targetPermanent); if (land == null) { continue; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorSourceEffect.java index 2bd064558e1..671f2901d4e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorSourceEffect.java @@ -71,8 +71,11 @@ public class BecomesColorSourceEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); + Player controller = game.getPlayer(source.getControllerId()); if (controller == null) { + discard(); return; } if (setColor == null) { @@ -86,7 +89,6 @@ public class BecomesColorSourceEffect extends ContinuousEffectImpl { game.informPlayers(controller.getLogName() + " has chosen the color: " + setColor.toString()); } } - super.init(source, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorTargetEffect.java index 9bd3be20097..6f2ae65c00a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesColorTargetEffect.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.abilities.effects.common.continuous; import java.util.UUID; @@ -52,35 +23,38 @@ import mage.players.Player; public class BecomesColorTargetEffect extends ContinuousEffectImpl { private ObjectColor setColor; + private final boolean retainColor; /** * Set the color of a spell or permanent - * - * @param duration */ public BecomesColorTargetEffect(Duration duration) { - this(null, duration, null); + this(null, duration); } public BecomesColorTargetEffect(ObjectColor setColor, Duration duration) { - this(setColor, duration, null); + this(setColor, false, duration); } - public BecomesColorTargetEffect(ObjectColor setColor, Duration duration, String text) { + public BecomesColorTargetEffect(ObjectColor setColor, boolean retainColor, Duration duration) { super(duration, Layer.ColorChangingEffects_5, SubLayer.NA, Outcome.Benefit); this.setColor = setColor; - staticText = text; + this.retainColor = retainColor; } protected BecomesColorTargetEffect(final BecomesColorTargetEffect effect) { super(effect); this.setColor = effect.setColor; + this.retainColor = effect.retainColor; } @Override public void init(Ability source, Game game) { + super.init(source, game); + Player controller = game.getPlayer(source.getControllerId()); if (controller == null) { + discard(); return; } if (setColor == null) { @@ -94,8 +68,6 @@ public class BecomesColorTargetEffect extends ContinuousEffectImpl { game.informPlayers(controller.getLogName() + " has chosen the color: " + setColor.toString()); } } - - super.init(source, game); } @Override @@ -106,12 +78,16 @@ public class BecomesColorTargetEffect extends ContinuousEffectImpl { } if (setColor != null) { boolean objectFound = false; - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { MageObject targetObject = game.getObject(targetId); if (targetObject != null) { if (targetObject instanceof Spell || targetObject instanceof Permanent) { objectFound = true; - targetObject.getColor(game).setColor(setColor); + if (retainColor) { + targetObject.getColor(game).addColor(setColor); + } else { + targetObject.getColor(game).setColor(setColor); + } } else { objectFound = false; } @@ -143,6 +119,9 @@ public class BecomesColorTargetEffect extends ContinuousEffectImpl { } else { sb.append(setColor.getDescription()); } + if (retainColor) { + sb.append(" in addition to its other colors"); + } if (!duration.toString().isEmpty()) { sb.append(' ').append(duration.toString()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java index c9266a7f3ae..4b4a8e3f362 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java @@ -80,7 +80,7 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { boolean result = false; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent == null) { continue; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTypeTargetEffect.java index b2fcdff6335..dd1109ebdc2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTypeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTypeTargetEffect.java @@ -52,7 +52,7 @@ public class BecomesCreatureTypeTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { boolean flag = false; - for (UUID targetPermanent : targetPointer.getTargets(game, source)) { + for (UUID targetPermanent : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetPermanent); if (permanent == null) { continue; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java index 833bcf58154..73a24776138 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java @@ -15,6 +15,8 @@ import mage.game.permanent.Permanent; import java.util.*; /** + * TODO: must be reworked to use same face down logic as BecomesFaceDownCreatureEffect + * * @author LevelX2 */ @@ -45,6 +47,8 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); + + // save permanents to become face down (one time usage on resolve) for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { if (!perm.isFaceDown(game) && !perm.isTransformable()) { affectedObjectList.add(new MageObjectReference(perm, game)); @@ -54,7 +58,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl { if (card != null) { for (Ability ability : card.getAbilities(game)) { if (ability instanceof MorphAbility) { - this.turnFaceUpAbilityMap.put(card.getId(), new TurnFaceUpAbility(((MorphAbility) ability).getMorphCosts())); + this.turnFaceUpAbilityMap.put(card.getId(), new TurnFaceUpAbility(((MorphAbility) ability).getFaceUpCosts())); } } } @@ -66,6 +70,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl { public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { boolean targetExists = false; for (MageObjectReference mor : affectedObjectList) { + // TODO: wtf, why it not use a BecomesFaceDownCreatureEffect.makeFaceDownObject and applied by layers?! Looks buggy Permanent permanent = mor.getPermanent(game); if (permanent != null && permanent.isFaceDown(game)) { targetExists = true; @@ -119,7 +124,6 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl { permanent.getPower().setModifiedBaseValue(2); permanent.getToughness().setModifiedBaseValue(2); } - } } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index c4a5c4859bd..472bd01489e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -1,41 +1,68 @@ package mage.abilities.effects.common.continuous; +import mage.MageObject; import mage.MageObjectReference; import mage.ObjectColor; import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.TurnFaceUpAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.keyword.WardAbility; import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.ModalDoubleFacedCard; +import mage.cards.repository.TokenInfo; +import mage.cards.repository.TokenRepository; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.token.EmptyToken; +import mage.game.permanent.token.Token; +import mage.util.CardUtil; +import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.List; +import java.util.UUID; /** + * Support different face down types: morph/manifest and disguise/cloak + *

    + * WARNING, if you use it custom effect then must create it for left side card, + * not main (see findDefaultCardSideForFaceDown) + * + *

    * This effect lets the card be a 2/2 face-down creature, with no text, no name, * no subtypes, and no mana cost, if it's face down on the battlefield. And it - * adds the a TurnFaceUpAbility ability. + * adds the TurnFaceUpAbility and other additional abilities + *

    + * Warning, if a card has multiple face down abilities then keep only one face up cost + * Example: Mischievous Quanar + * - a. Turn Mischievous Quanar face down - BecomesFaceDownCreatureEffect without turn up cost + * - b. Morph - BecomesFaceDownCreatureEffect with turn up cost inside * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { + private static final Logger logger = Logger.getLogger(BecomesFaceDownCreatureEffect.class); + public enum FaceDownType { MANIFESTED, MANUAL, - MEGAMORPHED, MORPHED, + MEGAMORPHED, DISGUISED, CLOAKED } protected int zoneChangeCounter; - protected Ability turnFaceUpAbility = null; + protected List additionalAbilities = new ArrayList<>(); protected MageObjectReference objectReference = null; protected boolean foundPermanent; protected FaceDownType faceDownType; @@ -60,9 +87,39 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { super(duration, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.BecomeCreature); this.objectReference = objectReference; this.zoneChangeCounter = Integer.MIN_VALUE; + + // add additional face up and information abilities if (turnFaceUpCosts != null) { - this.turnFaceUpAbility = new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED); + // face up for all + this.additionalAbilities.add(new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)); + + switch (faceDownType) { + case MORPHED: + case MEGAMORPHED: + // face up rules replace for cost hide + this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect( + "Turn it face up any time for its morph cost." + ))); + break; + case DISGUISED: + case CLOAKED: + // ward + this.additionalAbilities.add(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // face up rules replace for cost hide + this.additionalAbilities.add(new SimpleStaticAbility(Zone.ALL, new InfoEffect( + "Turn it face up any time for its disguise/cloaked cost." + ))); + break; + case MANUAL: + case MANIFESTED: + // no face up abilities + break; + default: + throw new IllegalArgumentException("Un-supported face down type: " + faceDownType); + } } + staticText = "{this} becomes a 2/2 face-down creature, with no text, no name, no subtypes, and no mana cost"; foundPermanent = false; this.faceDownType = faceDownType; @@ -71,9 +128,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { protected BecomesFaceDownCreatureEffect(final BecomesFaceDownCreatureEffect effect) { super(effect); this.zoneChangeCounter = effect.zoneChangeCounter; - if (effect.turnFaceUpAbility != null) { - this.turnFaceUpAbility = effect.turnFaceUpAbility.copy(); - } + effect.additionalAbilities.forEach(ability -> { + this.additionalAbilities.add(ability.copy()); + }); this.objectReference = effect.objectReference; this.foundPermanent = effect.foundPermanent; this.faceDownType = effect.faceDownType; @@ -123,62 +180,177 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { foundPermanent = true; switch (faceDownType) { case MANIFESTED: - case MANUAL: // sets manifested image + case MANUAL: // sets manifested image // TODO: wtf permanent.setManifested(true); break; case MORPHED: case MEGAMORPHED: permanent.setMorphed(true); break; + case DISGUISED: + permanent.setDisguised(true); + break; default: throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType); } } - - permanent.setName(EmptyNames.FACE_DOWN_CREATURE.toString()); - permanent.removeAllSuperTypes(game); - permanent.removeAllCardTypes(game); - permanent.addCardType(game, CardType.CREATURE); - permanent.removeAllSubTypes(game); - permanent.getColor(game).setColor(ObjectColor.COLORLESS); - Card card = game.getCard(permanent.getId()); - - List abilitiesToRemove = new ArrayList<>(); - for (Ability ability : permanent.getAbilities()) { - - // keep gained abilities from other sources, removes only own (card text) - if (card != null && !card.getAbilities().contains(ability)) { - continue; - } - - // 701.33c - // If a card with morph is manifested, its controller may turn that card face up using - // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up - // or the procedure described above to turn a manifested permanent face up. - // - // so keep all tune face up abilities and other face down compatible - if (ability.getWorksFaceDown()) { - ability.setRuleVisible(false); - continue; - } - - if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) { - if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) { - continue; - } - } - abilitiesToRemove.add(ability); - } - permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game); - if (turnFaceUpAbility != null) { // TODO: shouldn't be added by this effect, but separately - permanent.addAbility(turnFaceUpAbility, source.getSourceId(), game); - } - permanent.getPower().setModifiedBaseValue(2); - permanent.getToughness().setModifiedBaseValue(2); + makeFaceDownObject(game, source.getSourceId(), permanent, faceDownType, this.additionalAbilities); } else if (duration == Duration.Custom && foundPermanent) { discard(); } return true; } + // TODO: implement multiple face down types?! + public static FaceDownType findFaceDownType(Game game, Permanent permanent) { + if (permanent.isMorphed()) { + return BecomesFaceDownCreatureEffect.FaceDownType.MORPHED; + } else if (permanent.isDisguised()) { + return BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED; + } else if (permanent.isManifested()) { + return BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED; + } else if (permanent.isFaceDown(game)) { + return BecomesFaceDownCreatureEffect.FaceDownType.MANUAL; + } else { + return null; + } + } + + /** + * Convert any object (card, token) to face down (remove/hide all face up information and make it a 2/2 creature) + */ + public static void makeFaceDownObject(Game game, UUID sourceId, MageObject object, FaceDownType faceDownType, List additionalAbilities) { + String originalObjectInfo = object.toString(); + + // warning, it's a direct changes to the object (without game state, so no game param here) + object.setName(EmptyNames.FACE_DOWN_CREATURE.toString()); + object.removeAllSuperTypes(); + object.getSubtype().clear(); + object.removeAllCardTypes(); + object.addCardType(CardType.CREATURE); + object.getColor().setColor(ObjectColor.COLORLESS); + + // remove wrong abilities + Card card = game.getCard(object.getId()); + List abilitiesToRemove = new ArrayList<>(); + for (Ability ability : object.getAbilities()) { + + // keep gained abilities from other sources, removes only own (card text) + if (card != null && !card.getAbilities().contains(ability)) { + continue; + } + + // 701.33c + // If a card with morph is manifested, its controller may turn that card face up using + // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up + // or the procedure described above to turn a manifested permanent face up. + + // keep face down abilities active, but hide it from rules description + if (ability.getWorksFaceDown()) { + + // example: When Dog Walker is turned face up, create two tapped 1/1 white Dog creature tokens + ability.setRuleVisible(false); + + // becomes a 2/2 face-down creature - it hides a real ability too, but adds fake rule, see + if (!ability.getEffects().isEmpty()) { + if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) { + // enable for stack + ability.setRuleVisible(true); + } + } + continue; + } + + // all other can be removed + abilitiesToRemove.add(ability); + } + + // add additional abilities like face up (real ability hidden and duplicated with information without cost data) + if (object instanceof Permanent) { + // as permanent + Permanent permanentObject = (Permanent) object; + permanentObject.removeAbilities(abilitiesToRemove, sourceId, game); + if (additionalAbilities != null) { + additionalAbilities.forEach(blueprintAbility -> { + Ability newAbility = blueprintAbility.copy(); + newAbility.setRuleVisible(CardUtil.isInformationAbility(newAbility)); + permanentObject.addAbility(newAbility, sourceId, game); + }); + } + } else if (object instanceof CardImpl) { + // as card + CardImpl cardObject = (CardImpl) object; + cardObject.getAbilities().removeAll(abilitiesToRemove); + if (additionalAbilities != null) { + additionalAbilities.forEach(blueprintAbility -> { + Ability newAbility = blueprintAbility.copy(); + newAbility.setRuleVisible(CardUtil.isInformationAbility(newAbility)); + cardObject.addAbility(newAbility); + }); + } + } + + object.getPower().setModifiedBaseValue(2); + object.getToughness().setModifiedBaseValue(2); + + // image + String tokenName; + switch (faceDownType) { + case MORPHED: + case MEGAMORPHED: + tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH; + break; + case DISGUISED: + tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE; + break; + case MANIFESTED: + tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST; + break; + case CLOAKED: + tokenName = "TODO-CLOAKED"; + break; + case MANUAL: + tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL; + break; + default: + throw new IllegalArgumentException("Un-supported face down type for image: " + faceDownType); + } + + Token faceDownToken = new EmptyToken(); + TokenInfo faceDownInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(tokenName, object.getId()); + if (faceDownInfo != null) { + faceDownToken.setExpansionSetCode(faceDownInfo.getSetCode()); + faceDownToken.setCardNumber("0"); + faceDownToken.setImageFileName(faceDownInfo.getName()); + faceDownToken.setImageNumber(faceDownInfo.getImageNumber()); + } else { + logger.error("Can't find face down image for " + tokenName + ": " + originalObjectInfo); + // TODO: add default image like backface (warning, missing image info must be visible in card popup)? + } + + CardUtil.copySetAndCardNumber(object, faceDownToken); + + // hide rarity info + if (object instanceof Card) { + ((Card) object).setRarity(Rarity.SPECIAL); + } + } + + /** + * There are cards with multiple sides like MDFC, but face down must use only main/left side all the time. + * So try to find that side. + */ + public static Card findDefaultCardSideForFaceDown(Game game, Card card) { + // rules example: + // If a double-faced card is manifested, it will be put onto the battlefield face down. While face down, + // it can't transform. If the front face of the card is a creature card, you can turn it face up by paying + // its mana cost. If you do, its front face will be up. + + if (card instanceof ModalDoubleFacedCard) { + // only MDFC uses independent card sides on 2024 + return ((ModalDoubleFacedCard) card).getLeftHalfCard(); + } else { + return card; + } + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEnchantedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEnchantedEffect.java index 273bfe0c30c..0d935b91357 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEnchantedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEnchantedEffect.java @@ -69,7 +69,7 @@ public class BoostEnchantedEffect extends ContinuousEffectImpl { public boolean apply(Game game, Ability source) { Permanent permanent = null; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEquippedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEquippedEffect.java index 713b5aaf15c..2cc3ce3b1bb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEquippedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostEquippedEffect.java @@ -65,14 +65,14 @@ public class BoostEquippedEffect extends ContinuousEffectImpl { this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game)); } } - super.init(source, game); // inits the target pointer so call it after setting the targetPointer + super.init(source, game); // must call at the end due target pointer setup } @Override public boolean apply(Game game, Ability source) { Permanent creature = null; if (fixedTarget) { - creature = game.getPermanent(targetPointer.getFirst(game, source)); + creature = game.getPermanent(getTargetPointer().getFirst(game, source)); } else { Permanent equipment = game.getPermanent(source.getSourceId()); if (equipment != null && equipment.getAttachedTo() != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostTargetEffect.java index d1c3cb3bda3..dd4df0f0408 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostTargetEffect.java @@ -68,7 +68,7 @@ public class BoostTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent target = game.getPermanent(permanentId); if (target != null && target.isCreature(game)) { target.addPower(power.calculate(game, source, this)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/ExchangeControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/ExchangeControlTargetEffect.java index 1df94bc0e22..03bfa39656c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/ExchangeControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/ExchangeControlTargetEffect.java @@ -73,14 +73,16 @@ public class ExchangeControlTargetEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); + Permanent permanent1 = null; Permanent permanent2 = null; if (withSource) { - permanent1 = game.getPermanent(targetPointer.getFirst(game, source)); + permanent1 = game.getPermanent(getTargetPointer().getFirst(game, source)); permanent2 = game.getPermanent(source.getSourceId()); } else { - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { if (permanent1 == null) { permanent1 = game.getPermanent(permanentId); } else if (permanent2 == null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java index d07c2b053d1..3c6ea1ea00b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java @@ -77,20 +77,20 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { - super.init(source, game); if (affectedObjectsSet) { Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (equipment != null && equipment.getAttachedTo() != null) { this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo()))); } } + super.init(source, game); // must call at the end due target pointer setup } @Override public boolean apply(Game game, Ability source) { Permanent permanent; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java index bcc35060f39..3d33102d124 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java @@ -138,6 +138,8 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl { } if (forceQuotes || gainedAbility.startsWith("When") || gainedAbility.startsWith("{T}")) { gainedAbility = '"' + gainedAbility + '"'; + } else { + gainedAbility = CardUtil.getTextWithFirstCharLowerCase(gainedAbility); } sb.append(gainedAbility); if (!durationRuleAtStart && !duration.toString().isEmpty() && duration != Duration.EndOfGame) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java index cbeb2b8c34c..d2243674730 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java @@ -4,7 +4,7 @@ import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; import mage.constants.*; -import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; import mage.game.Game; import mage.game.stack.Spell; import mage.game.stack.StackObject; @@ -17,9 +17,9 @@ import mage.util.CardUtil; public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl { private final Ability ability; - private final FilterCard filter; + private final FilterNonlandCard filter; - public GainAbilityControlledSpellsEffect(Ability ability, FilterCard filter) { + public GainAbilityControlledSpellsEffect(Ability ability, FilterNonlandCard filter) { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.ability = ability; this.filter = filter; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java index 3e1dd1172eb..224bac2e96a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java @@ -60,7 +60,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl { // must support dynamic targets from static ability and static targets from activated abilities if (this.affectedObjectsSet) { // target permanents (by default) - targetPointer.getTargets(game, source) + getTargetPointer().getTargets(game, source) .stream() .map(game::getPermanent) .filter(Objects::nonNull) @@ -70,7 +70,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl { // target cards with linked permanents if (this.useOnCard) { - targetPointer.getTargets(game, source) + getTargetPointer().getTargets(game, source) .stream() .map(game::getCard) .filter(Objects::nonNull) @@ -147,7 +147,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl { } } else { // DYNAMIC TARGETS - for (UUID objectId : targetPointer.getTargets(game, source)) { + for (UUID objectId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(objectId); if (permanent != null) { permanent.addAbility(ability, source.getSourceId(), game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java index 8cfe2280bbd..1a9ee587149 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java @@ -60,20 +60,20 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { - super.init(source, game); if (affectedObjectsSet) { Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (equipment != null && equipment.getAttachedTo() != null) { this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo()))); } } + super.init(source, game); // must call at the end due target pointer setup } @Override public boolean apply(Game game, Ability source) { Permanent permanent = null; if (affectedObjectsSet) { - permanent = game.getPermanent(targetPointer.getFirst(game, source)); + permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { discard(); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAllCreatureTypesTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAllCreatureTypesTargetEffect.java index d458f45e0e9..4ac463b8ec8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAllCreatureTypesTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAllCreatureTypesTargetEffect.java @@ -33,7 +33,7 @@ public class GainAllCreatureTypesTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent target = game.getPermanent(permanentId); if (target != null) { target.setIsAllCreatureTypes(game, true); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlAllUntapGainHasteEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlAllUntapGainHasteEffect.java new file mode 100644 index 00000000000..5aade80fd9c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlAllUntapGainHasteEffect.java @@ -0,0 +1,67 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.keyword.HasteAbility; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.List; + +/** + * @author xenohedron + */ +public class GainControlAllUntapGainHasteEffect extends OneShotEffect { + + private final FilterPermanent filter; + + /** + * Gain control of all [filter] until end of turn. Untap them. They gain haste until end of turn. + */ + public GainControlAllUntapGainHasteEffect(FilterPermanent filter) { + super(Outcome.GainControl); + this.filter = filter; + makeText("them"); + } + + protected GainControlAllUntapGainHasteEffect(final GainControlAllUntapGainHasteEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public GainControlAllUntapGainHasteEffect copy() { + return new GainControlAllUntapGainHasteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List affectedObjects = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game); + if (affectedObjects.isEmpty()) { + return false; + } + FilterPermanent affectedFilter = new FilterPermanent(); + affectedFilter.add(new PermanentReferenceInCollectionPredicate(affectedObjects, game)); + new GainControlAllEffect(Duration.EndOfTurn, affectedFilter).apply(game, source); + game.getState().processAction(game); + new UntapAllEffect(affectedFilter).apply(game, source); + game.addEffect(new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, affectedFilter), source); + return true; + } + + private void makeText(String those) { + this.staticText = "gain control of all " + filter.getMessage() + " until end of turn. Untap " + + those + ". They gain haste until end of turn"; + } + + public GainControlAllUntapGainHasteEffect withTextOptions(String those) { + makeText(those); + return this; + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java index 3549d825d8c..833f3f48c0e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java @@ -21,7 +21,7 @@ import java.util.UUID; public class GainControlTargetEffect extends ContinuousEffectImpl { protected UUID controllingPlayerId; - private boolean fixedControl; + private final boolean fixedControl; private boolean firstControlChange = true; private final Condition condition; @@ -63,6 +63,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl { this.controllingPlayerId = effect.controllingPlayerId; this.fixedControl = effect.fixedControl; this.condition = effect.condition; + this.firstControlChange = effect.firstControlChange; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseAllAbilitiesTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseAllAbilitiesTargetEffect.java index 0dd21369370..614be0bd431 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseAllAbilitiesTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseAllAbilitiesTargetEffect.java @@ -35,7 +35,7 @@ public class LoseAllAbilitiesTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - for (UUID permanentId : targetPointer.getTargets(game, source)) { + for (UUID permanentId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { permanent.removeAllAbilities(source.getSourceId(), game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java index 08af68fd1f7..da046006396 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java @@ -41,7 +41,7 @@ public class LoseArtifactTypeTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (UUID targetId : targetPointer.getTargets(game, source)) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { if (targetId == null) { continue; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayWithHandRevealedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayWithHandRevealedEffect.java index 220470f6cb1..824dd5d6765 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayWithHandRevealedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayWithHandRevealedEffect.java @@ -57,7 +57,7 @@ public class PlayWithHandRevealedEffect extends ContinuousEffectImpl { for (UUID playerID : affectedPlayers) { Player player = game.getPlayer(playerID); if (player != null) { - player.revealCards(player.getName() + "'s hand cards", player.getHand(), game, false); + player.revealCards("Cards in " + player.getName() + "'s hand", player.getHand(), game, false); } } return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java index f53a6d89f6b..d3c8314802e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java @@ -57,7 +57,7 @@ public class AddCountersTargetEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source); if (controller != null && sourceObject != null && counter != null) { int affectedTargets = 0; - for (UUID uuid : targetPointer.getTargets(game, source)) { + for (UUID uuid : getTargetPointer().getTargets(game, source)) { Counter newCounter = counter.copy(); int calculated = amount.calculate(game, source, this); // 0 -- you must use default couner if (calculated < 0) { @@ -72,7 +72,7 @@ public class AddCountersTargetEffect extends OneShotEffect { Permanent permanent = game.getPermanent(uuid); Player player = game.getPlayer(uuid); - Card card = game.getCard(targetPointer.getFirst(game, source)); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (permanent != null) { permanent.addCounters(newCounter, source.getControllerId(), source, game); affectedTargets++; @@ -97,7 +97,7 @@ public class AddCountersTargetEffect extends OneShotEffect { @Override public String getText(Mode mode) { - if (!staticText.isEmpty()) { + if (staticText != null && !staticText.isEmpty()) { return staticText; } return CardUtil.getAddRemoveCountersText(amount, counter, getTargetPointer().describeTargets(mode.getTargets(), "that creature"), true); diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java index 9fc8ffd3871..c2f35e9449b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java @@ -38,7 +38,7 @@ public class AddPoisonCounterTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.addCounters(CounterType.POISON.createInstance(amount), source.getControllerId(), source, game); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/MoveCountersTargetsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/MoveCountersTargetsEffect.java index abb1233f47b..e797f82c7e2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/MoveCountersTargetsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/MoveCountersTargetsEffect.java @@ -37,8 +37,8 @@ public class MoveCountersTargetsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent removeTargetCreature = game.getPermanent(targetPointer.getTargets(game, source).get(0)); - Permanent addTargetCreature = game.getPermanent(targetPointer.getTargets(game, source).get(1)); + Permanent removeTargetCreature = game.getPermanent(getTargetPointer().getTargets(game, source).get(0)); + Permanent addTargetCreature = game.getPermanent(getTargetPointer().getTargets(game, source).get(1)); if (removeTargetCreature != null && addTargetCreature != null && removeTargetCreature.getCounters(game).getCount(counterType) >= amount) { removeTargetCreature.removeCounters(counterType.createInstance(amount), source, game); addTargetCreature.addCounters(counterType.createInstance(amount), source.getControllerId(), source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java index f6170abe556..cb135ba07bb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/RemoveAllCountersTargetEffect.java @@ -29,7 +29,7 @@ public class RemoveAllCountersTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if(permanent != null) { int count = permanent.getCounters(game).getCount(counterType); permanent.removeCounters(counterType.getName(), count, source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardCardYouChooseTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardCardYouChooseTargetEffect.java index a7fca9f5ea2..8202f9cbfa8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardCardYouChooseTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardCardYouChooseTargetEffect.java @@ -89,7 +89,7 @@ public class DiscardCardYouChooseTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); if (player == null || controller == null) { return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardTargetEffect.java index 9c22850fdf3..c4bdeafe918 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/DiscardTargetEffect.java @@ -58,7 +58,7 @@ public class DiscardTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID targetPlayerId : targetPointer.getTargets(game, source)) { + for (UUID targetPlayerId : getTargetPointer().getTargets(game, source)) { Player player = game.getPlayer(targetPlayerId); if (player != null) { player.discard(amount.calculate(game, source, this), randomDiscard, false, source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CombatDamageByToughnessTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CombatDamageByToughnessTargetEffect.java index 36da6ea92d2..125a6e5d02b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CombatDamageByToughnessTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CombatDamageByToughnessTargetEffect.java @@ -33,7 +33,7 @@ public class CombatDamageByToughnessTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Set set = targetPointer.getTargets(game, source).stream() + Set set = getTargetPointer().getTargets(game, source).stream() .map(game::getPermanent) .filter(Objects::nonNull) .collect(Collectors.toSet()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect.java new file mode 100644 index 00000000000..ab7e06d9e6e --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect.java @@ -0,0 +1,77 @@ +package mage.abilities.effects.common.search; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.SearchEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; + +/** + * @author BetaSteward_at_googlemail.com, edited by Cguy7777 + */ +public class SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect extends SearchEffect { + + private static final FilterCard filter = new FilterCard("card to put on the battlefield tapped"); + + public SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(TargetCardInLibrary target) { + super(target, Outcome.PutLandInPlay); + staticText = "search your library for " + target.getDescription() + + ", reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle"; + } + + protected SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(final SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect effect) { + super(effect); + } + + @Override + public SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect copy() { + return new SearchLibraryPutOneOntoBattlefieldTappedRestInHandEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = game.getObject(source); + if (controller == null || sourceObject == null) { + return false; + } + + if (controller.searchLibrary(target, source, game)) { + if (!target.getTargets().isEmpty()) { + Cards revealed = new CardsImpl(target.getTargets()); + controller.revealCards(sourceObject.getIdName(), revealed, game); + + if (target.getTargets().size() >= 2) { + TargetCardInLibrary targetCardToBattlefield = new TargetCardInLibrary(filter); + controller.choose(Outcome.PutLandInPlay, revealed, targetCardToBattlefield, source, game); + + Card cardToBattlefield = revealed.get(targetCardToBattlefield.getFirstTarget(), game); + Cards cardsToHand = new CardsImpl(revealed); + if (cardToBattlefield != null) { + controller.moveCards(cardToBattlefield, Zone.BATTLEFIELD, source, game, true, false, false, null); + cardsToHand.remove(cardToBattlefield); + } + + controller.moveCardsToHandWithInfo(cardsToHand, source, game, true); + } else if (target.getTargets().size() == 1) { + Cards cards = new CardsImpl(revealed); + Card cardToBattlefield = cards.getRandom(game); + if (cardToBattlefield != null) { + controller.moveCards(cardToBattlefield, Zone.BATTLEFIELD, source, game, true, false, false, null); + } + } + } + controller.shuffleLibrary(source, game); + return true; + } + controller.shuffleLibrary(source, game); + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/turn/SkipNextTurnSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/turn/SkipNextTurnSourceEffect.java index 47f70b04e95..7ef4477b83a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/turn/SkipNextTurnSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/turn/SkipNextTurnSourceEffect.java @@ -41,7 +41,7 @@ public class SkipNextTurnSourceEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { UUID playerId = null; if (source instanceof ActivatedAbilityImpl) { - playerId = ((ActivatedAbilityImpl) source).getActivatorId(); + playerId = ((ActivatedAbilityImpl) source).getActivatorId(); // for Lethal Vapors } if (playerId == null) { playerId = source.getControllerId(); diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/BolsterEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/BolsterEffect.java index 008f9393378..ff4fdd8e102 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/BolsterEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/BolsterEffect.java @@ -94,12 +94,12 @@ public class BolsterEffect extends OneShotEffect { return false; } Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(amount.calculate(game, source, this))); - FixedTarget fixedTarget = new FixedTarget(selectedCreature, game); - effect.setTargetPointer(fixedTarget); + FixedTarget blueprintTarget = new FixedTarget(selectedCreature, game); + effect.setTargetPointer(blueprintTarget.copy()); effect.apply(game, source); if (!additionalEffects.isEmpty()) { for (Effect additionalEffect : additionalEffects) { - additionalEffect.setTargetPointer(fixedTarget); + additionalEffect.setTargetPointer(blueprintTarget.copy()); if (additionalEffect instanceof OneShotEffect) { additionalEffect.apply(game, source); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/FatesealEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/FatesealEffect.java index 81332c811ac..a8fffd829a5 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/FatesealEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/FatesealEffect.java @@ -48,8 +48,6 @@ public class FatesealEffect extends OneShotEffect { if (opponent == null) { return false; } - boolean revealed = opponent.isTopCardRevealed(); // by looking at the cards with fateseal you have not to reveal the next card - opponent.setTopCardRevealed(false); Cards cards = new CardsImpl(); int count = Math.min(fatesealNumber, opponent.getLibrary().size()); if (count == 0) { @@ -76,7 +74,6 @@ public class FatesealEffect extends OneShotEffect { // move cards to the top of the library controller.putCardsOnTopOfLibrary(cards, game, source, true); game.fireEvent(new GameEvent(GameEvent.EventType.FATESEALED, opponent.getId(), source, source.getControllerId())); - controller.setTopCardRevealed(revealed); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java index 9d54181fc11..6fd9a6c8315 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java @@ -28,9 +28,21 @@ public class InvestigateEffect extends OneShotEffect { } public InvestigateEffect(DynamicValue amount) { + this(amount, true); + } + + public InvestigateEffect(boolean showAbilityHint) { + this(1, showAbilityHint); + } + + public InvestigateEffect(int amount, boolean showAbilityHint) { + this(StaticValue.get(amount), showAbilityHint); + } + + public InvestigateEffect(DynamicValue amount, boolean showAbilityHint) { super(Outcome.Benefit); this.amount = amount; - this.staticText = makeText(); + this.staticText = makeText(showAbilityHint); } protected InvestigateEffect(final InvestigateEffect effect) { @@ -63,7 +75,7 @@ public class InvestigateEffect extends OneShotEffect { return new InvestigateEffect(this); } - private String makeText() { + private String makeText(boolean showAbilityHint) { String message; if (amount instanceof StaticValue) { int value = ((StaticValue) amount).getValue(); @@ -80,7 +92,8 @@ public class InvestigateEffect extends OneShotEffect { } else { message = " X times, where X is the " + amount.getMessage() + ". (To investigate, c"; } - return "investigate" + message + "reate a Clue token. " + + String finalMessage = "investigate" + message + "reate a Clue token. " + "It's an artifact with \"{2}, Sacrifice this artifact: Draw a card.\")"; + return showAbilityHint ? finalMessage : CardUtil.stripReminderText(finalMessage); } } diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java index 102b40239b4..52c9b8d86b3 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.keyword; import mage.MageObjectReference; @@ -22,7 +21,45 @@ import mage.util.CardUtil; import java.util.Set; /** - * @author LevelX2 + * Manifest + *

    + * 701.34a + * To manifest a card, turn it face down. It becomes a 2/2 face-down creature card with no text, no name, no subtypes, + * and no mana cost. Put that card onto the battlefield face down. That permanent is a manifested permanent for as + * long as it remains face down. The effect defining its characteristics works while the card is face down and ends + * when it’s turned face up. + *

    + * 701.34b + * Any time you have priority, you may turn a manifested permanent you control face up. This is a special action + * that doesn’t use the stack (see rule 116.2b). To do this, show all players that the card representing that + * permanent is a creature card and what that card’s mana cost is, pay that cost, then turn the permanent face up. + * The effect defining its characteristics while it was face down ends, and it regains its normal characteristics. + * (If the card representing that permanent isn’t a creature card or it doesn’t have a mana cost, it can’t be turned + * face up this way.) + *

    + * 701.34c TODO: need support it + * If a card with morph is manifested, its controller may turn that card face up using either the procedure + * described in rule 702.37e to turn a face-down permanent with morph face up or the procedure described above + * to turn a manifested permanent face up. + *

    + * 701.34d TODO: need support it + * If a card with disguise is manifested, its controller may turn that card face up using either the procedure + * described in rule 702.168d to turn a face-down permanent with disguise face up or the procedure described + * above to turn a manifested permanent face up. + *

    + * 701.34e TODO: need support it + * If an effect instructs a player to manifest multiple cards from their library, those cards are manifested one at a time. + *

    + * 701.34f TODO: need research, possible buggy (see face down effect order doManifestCards) + * If an effect instructs a player to manifest a card and a rule or effect prohibits the face-down object from + * entering the battlefield, that card isn’t manifested. Its characteristics remain unmodified and it remains in + * its previous zone. If it was face up, it remains face up. + *

    + * 701.34g TODO: need support it + * If a manifested permanent that’s represented by an instant or sorcery card would turn face up, its controller + * reveals it and leaves it face down. Abilities that trigger whenever a permanent is turned face up won’t trigger. + * + * @author LevelX2, JayDi85 */ public class ManifestEffect extends OneShotEffect { @@ -58,33 +95,64 @@ public class ManifestEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - int value = amount.calculate(game, source, this); - Set cards = controller.getLibrary().getTopCards(game, value); - for (Card card : cards) { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); - - } - controller.moveCards(cards, Zone.BATTLEFIELD, source, game, false, true, false, null); - for (Card card : cards) { - Permanent permanent = game.getPermanent(card.getId()); - if (permanent != null) { - permanent.setManifested(true); - } - } - return true; + if (controller == null) { + return false; } - return false; + + int manifestAmount = amount.calculate(game, source, this); + return doManifestCards(game, source, controller, controller.getLibrary().getTopCards(game, manifestAmount)); + } + + public static boolean doManifestCards(Game game, Ability source, Player manifestPlayer, Set cardsToManifest) { + if (cardsToManifest.isEmpty()) { + return false; + } + + // prepare source ability + // TODO: looks buggy, must not change source ability! + // TODO: looks buggy, if target player manifested then source's controllerId will be wrong (not who manifested) + // so BecomesFaceDownCreatureEffect will see wrong source.controllerId + // (possible bugs: keep manifested after player leave/lose?) + Ability newSource = source.copy(); + newSource.setWorksFaceDown(true); + + // prepare face down effect for battlefield permanents + // TODO: need research - why it add effect before move?! + for (Card card : cardsToManifest) { + Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card); + + // search mana cost for a face up ability (look at face side of the double side card) + ManaCosts manaCosts = null; + if (battlefieldCard.isCreature(game)) { + manaCosts = battlefieldCard.getSpellAbility() != null ? battlefieldCard.getSpellAbility().getManaCosts() : null; + if (manaCosts == null) { + manaCosts = new ManaCostsImpl<>("{0}"); + } + } + + // zcc + 1 for use case with Rally the Ancestors (see related test) + MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game); + game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); + } + + // move cards to battlefield as face down + // TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate) + manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null); + for (Card card : cardsToManifest) { + Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card); + + Permanent permanent = game.getPermanent(battlefieldCard.getId()); + if (permanent != null) { + // TODO: permanent already has manifested status, so code can be deleted later + // TODO: add test with battlefield trigger/watcher (must not see normal card, must not see face down status without manifest) + permanent.setManifested(true); + } else { + // TODO: looks buggy, card can't be moved to battlefield, but face down effect already active + // or it can be face down on another move to battlefield + } + } + + return true; } private String setText() { diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java index 23e0dfba3a8..599190c855f 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestTargetPlayerEffect.java @@ -1,24 +1,12 @@ - package mage.abilities.effects.keyword; -import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType; -import mage.cards.Card; -import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import java.util.Set; - /** * @author LevelX2 */ @@ -48,31 +36,11 @@ public class ManifestTargetPlayerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (targetPlayer != null) { - Ability newSource = source.copy(); - newSource.setWorksFaceDown(true); - Set cards = targetPlayer.getLibrary().getTopCards(game, amount); - for (Card card : cards) { - ManaCosts manaCosts = null; - if (card.isCreature(game)) { - manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; - if (manaCosts == null) { - manaCosts = new ManaCostsImpl<>("{0}"); - } - } - MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game); - game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource); - } - targetPlayer.moveCards(cards, Zone.BATTLEFIELD, source, game, false, true, false, null); - for (Card card : cards) { - Permanent permanent = game.getPermanent(card.getId()); - if (permanent != null) { - permanent.setManifested(true); - } - } - return true; + if (targetPlayer == null) { + return false; } - return false; + + return ManifestEffect.doManifestCards(game, source, targetPlayer, targetPlayer.getLibrary().getTopCards(game, amount)); } private String setText() { diff --git a/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java b/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java index d3763d494f4..84a151a1c53 100644 --- a/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/AwakenAbility.java @@ -1,7 +1,6 @@ package mage.abilities.keyword; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -20,7 +19,6 @@ import mage.target.Target; import mage.target.common.TargetControlledPermanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; -import org.apache.log4j.Logger; import java.util.UUID; @@ -29,12 +27,8 @@ import java.util.UUID; */ public class AwakenAbility extends SpellAbility { - private static final Logger logger = Logger.getLogger(AwakenAbility.class); - - private static final String filterMessage = "a land you control to awake"; - private final String rule; - private final int awakenValue; + private static final FilterControlledLandPermanent filter = new FilterControlledLandPermanent(AwakenEffect.filterMessage); public AwakenAbility(Card card, int awakenValue, String awakenCosts) { super(card.getSpellAbility()); @@ -47,9 +41,9 @@ public class AwakenAbility extends SpellAbility { this.clearManaCostsToPay(); this.addCost(new ManaCostsImpl<>(awakenCosts)); - this.addTarget(new TargetControlledPermanent(new FilterControlledLandPermanent(filterMessage))); - this.addEffect(new AwakenEffect()); - this.awakenValue = awakenValue; + this.addTarget(new TargetControlledPermanent(filter)); + this.addEffect(new AwakenEffect(awakenValue)); + rule = "Awaken " + awakenValue + "—" + awakenCosts + " (If you cast this spell for " + awakenCosts + ", also put " + CardUtil.getOneOneCountersText(awakenValue) @@ -58,7 +52,6 @@ public class AwakenAbility extends SpellAbility { protected AwakenAbility(final AwakenAbility ability) { super(ability); - this.awakenValue = ability.awakenValue; this.rule = ability.rule; } @@ -77,56 +70,52 @@ public class AwakenAbility extends SpellAbility { return rule; } - class AwakenEffect extends OneShotEffect { +} - private AwakenEffect() { - super(Outcome.BoostCreature); - this.staticText = "put " + CardUtil.getOneOneCountersText(awakenValue) +" on target land you control"; - } +class AwakenEffect extends OneShotEffect { - protected AwakenEffect(final AwakenEffect effect) { - super(effect); - } + static final String filterMessage = "a land you control to awake"; - @Override - public AwakenEffect copy() { - return new AwakenEffect(this); - } + private final int awakenValue; - @Override - public boolean apply(Game game, Ability source) { - UUID targetId = null; - if (source != null && source.getTargets() != null) { - for (Target target : source.getTargets()) { - if (target.getFilter() != null && target.getFilter().getMessage().equals(filterMessage)) { - targetId = target.getFirstTarget(); - } - } - if (targetId != null) { - FixedTarget fixedTarget = new FixedTarget(targetId, game); - ContinuousEffect continuousEffect = new BecomesCreatureTargetEffect(new AwakenElementalToken(), false, true, Duration.Custom); - continuousEffect.setTargetPointer(fixedTarget); - game.addEffect(continuousEffect, source); - Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(awakenValue)); - effect.setTargetPointer(fixedTarget); - return effect.apply(game, source); - } - } else // source should never be null, but we are seeing a lot of NPEs from this section - if (source == null) { - logger.fatal("Source was null in AwakenAbility: Create a bug report or fix the source code"); - } else if (source.getTargets() == null) { - MageObject sourceObj = source.getSourceObject(game); - if (sourceObj != null) { - Class sourceClass = sourceObj.getClass(); - if (sourceClass != null) { - logger.fatal("getTargets was null in AwakenAbility for " + sourceClass.toString() + " : Create a bug report or fix the source code"); - } - } - } - return true; - } + AwakenEffect(int awakenValue) { + super(Outcome.BoostCreature); + this.awakenValue = awakenValue; + this.staticText = "put " + CardUtil.getOneOneCountersText(awakenValue) + " on target land you control and it becomes a 0/0 Elemental creature with haste"; } + private AwakenEffect(final AwakenEffect effect) { + super(effect); + this.awakenValue = effect.awakenValue; + } + + @Override + public AwakenEffect copy() { + return new AwakenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID targetId = null; + if (source != null && source.getTargets() != null) { + for (Target target : source.getTargets()) { + if (target.getFilter() != null && target.getFilter().getMessage().equals(filterMessage)) { + targetId = target.getFirstTarget(); + } + } + if (targetId != null) { + FixedTarget blueprintTarget = new FixedTarget(targetId, game); + ContinuousEffect continuousEffect = new BecomesCreatureTargetEffect(new AwakenElementalToken(), false, true, Duration.Custom); + continuousEffect.setTargetPointer(blueprintTarget.copy()); + game.addEffect(continuousEffect, source); + Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(awakenValue)); + effect.setTargetPointer(blueprintTarget.copy()); + effect.apply(game, source); + } + return true; + } + return false; + } } class AwakenElementalToken extends TokenImpl { diff --git a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java index 6516b44405e..13bc3982967 100644 --- a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java @@ -115,7 +115,7 @@ public class BestowAbility extends SpellAbility { public static void becomeCreature(Permanent permanent, Game game) { // permanently changes to the object if (permanent != null) { - MageObject basicObject = permanent.getBasicMageObject(game); + MageObject basicObject = permanent.getBasicMageObject(); if (basicObject != null) { game.checkStateAndTriggered(); // Bug #8157 basicObject.getSubtype().remove(SubType.AURA); @@ -164,7 +164,7 @@ class BestowEntersBattlefieldEffect extends ReplacementEffectImpl { } // change types permanently - MageObject basicObject = bestowPermanent.getBasicMageObject(game); + MageObject basicObject = bestowPermanent.getBasicMageObject(); if (basicObject != null && !basicObject.getSubtype().contains(SubType.AURA)) { basicObject.addSubType(SubType.AURA); basicObject.removeCardType(CardType.CREATURE); diff --git a/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java b/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java index 629ac59f895..b93d5e313fd 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java @@ -1,22 +1,91 @@ package mage.abilities.keyword; +import mage.abilities.Ability; import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.cards.Card; +import mage.constants.SpellAbilityCastMode; +import mage.constants.SpellAbilityType; +import mage.constants.TimingRule; /** - * @author TheElk801 - * TODO: Implement this + * 702.168. Disguise + *

    + * 702.168a + * Disguise is a static ability that functions in any zone from which you could play the card it’s on, + * and the disguise effect works any time the card is face down. “Disguise [cost]” means “You may cast this + * card as a 2/2 face-down creature with ward {2}, no name, no subtypes, and no mana cost by paying {3} rather + * than paying its mana cost.” (See rule 708, “Face-Down Spells and Permanents.”) + *

    + * 702.168b + * To cast a card using its disguise ability, turn the card face down and announce that you are using a disguise ability. + * It becomes a 2/2 face-down creature card with ward {2}, no name, no subtypes, and no mana cost. Any effects or + * prohibitions that would apply to casting a card with these characteristics (and not the face-up card’s characteristics) + * are applied to casting this card. These values are the copiable values of that object’s characteristics. + * (See rule 613, “Interaction of Continuous Effects,” and rule 707, “Copying Objects.”) Put it onto the stack + * (as a face-down spell with the same characteristics), and pay {3} rather than pay its mana cost. This follows the + * rules for paying alternative costs. You can use a disguise ability to cast a card from any zone from which you + * could normally cast it. When the spell resolves, it enters the battlefield with the same characteristics the spell + * had. The disguise effect applies to the face-down object wherever it is, and it ends when the permanent is turned + * face up. + *

    + * 702.168c + * You can’t normally cast a card face down. A disguise ability allows you to do so. + *

    + * 702.168d + * Any time you have priority, you may turn a face-down permanent you control with a disguise ability face up. + * This is a special action; it doesn’t use the stack (see rule 116). To do this, show all players what the + * permanent’s disguise cost would be if it were face up, pay that cost, then turn the permanent face up. + * (If the permanent wouldn’t have a disguise cost if it were face up, it can’t be turned face up this way.) + * The disguise effect on it ends, and it regains its normal characteristics. Any abilities relating to the + * permanent entering the battlefield don’t trigger when it’s turned face up and don’t have any effect, + * because the permanent has already entered the battlefield. + *

    + * 702.168e + * If a permanent’s disguise cost includes X, other abilities of that permanent may also refer to X. + * The value of X in those abilities is equal to the value of X chosen as the disguise special action was taken. + *

    + * 702.168f + * See rule 708, “Face-Down Spells and Permanents,” for more information about how to cast cards with a disguise ability. + *

    + *

    + * MorphAbility as a reference implementation + * + * @author JayDi85 */ public class DisguiseAbility extends SpellAbility { + protected static final String ABILITY_KEYWORD = "Disguise"; + protected static final String REMINDER_TEXT = "You may cast this card face down for {3} as a 2/2 creature with " + + "ward {2}. Turn it face up any time for its disguise cost."; + + protected Costs disguiseCosts; + public DisguiseAbility(Card card, Cost disguiseCost) { super(new GenericManaCost(3), card.getName()); + this.timing = TimingRule.SORCERY; + this.disguiseCosts = new CostsImpl<>(); + this.disguiseCosts.add(disguiseCost); + this.setSpellAbilityCastMode(SpellAbilityCastMode.DISGUISE); + this.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE); + + // face down effect (hidden by default, visible in face down objects) + Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect( + this.disguiseCosts, BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED)); + ability.setWorksFaceDown(true); + ability.setRuleVisible(false); + addSubAbility(ability); } private DisguiseAbility(final DisguiseAbility ability) { super(ability); + this.disguiseCosts = ability.disguiseCosts; // can't be changed TODO: looks buggy, need research } @Override @@ -24,8 +93,16 @@ public class DisguiseAbility extends SpellAbility { return new DisguiseAbility(this); } + public Costs getFaceUpCosts() { + return this.disguiseCosts; + } + @Override public String getRule() { - return "Disguise"; + boolean isMana = disguiseCosts.get(0) instanceof ManaCost; + String costInfo = this.disguiseCosts.getText() + (isMana ? " " : ". "); + return ABILITY_KEYWORD + (isMana ? " " : "—") + + costInfo + + " (" + REMINDER_TEXT + ")"; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java index 564b94d841a..76b469d2e59 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java @@ -5,11 +5,17 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.AttachEffect; import mage.constants.Outcome; +import mage.constants.TargetController; import mage.constants.TimingRule; import mage.constants.Zone; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.target.Target; import mage.target.common.TargetControlledCreaturePermanent; +import java.util.ArrayList; +import java.util.List; + /** * @author BetaSteward_at_googlemail.com */ @@ -40,6 +46,23 @@ public class EquipAbility extends ActivatedAbilityImpl { public EquipAbility(Outcome outcome, Cost cost, Target target, boolean showAbilityHint) { super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Equip"), cost); + + // verify check: only controlled target allowed + // 702.6c + // Equip abilities may further restrict what creatures may be chosen as legal targets. + // Such restrictions usually appear in the form “Equip [quality]” or “Equip [quality] creature.” + // These equip abilities may legally target only a creature that’s controlled by the player + // activating the ability and that has the chosen quality. Additional restrictions for an equip + // ability don’t restrict what the Equipment may be attached to. + List list = new ArrayList<>(); + Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list); + if (list.stream() + .filter(p -> p instanceof TargetController.ControllerPredicate) + .map(p -> (TargetController.ControllerPredicate) p) + .noneMatch(p -> p.getController().equals(TargetController.YOU))) { + throw new IllegalArgumentException("Wrong code usage: equip ability must use target/filter with controller predicate - " + target); + } + this.addTarget(target); this.timing = TimingRule.SORCERY; this.showAbilityHint = showAbilityHint; diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index f98f4299405..b7192f9f187 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -90,13 +90,13 @@ public class ForetellAbility extends SpecialAction { return " foretells a card from hand"; } - public class ForetellExileEffect extends OneShotEffect { + static class ForetellExileEffect extends OneShotEffect { private final Card card; String foretellCost; String foretellSplitCost; - public ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { + ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { super(Outcome.Neutral); this.card = card; this.foretellCost = foretellCost; @@ -150,9 +150,9 @@ public class ForetellAbility extends SpecialAction { } } - public class ForetellLookAtCardEffect extends AsThoughEffectImpl { + static class ForetellLookAtCardEffect extends AsThoughEffectImpl { - public ForetellLookAtCardEffect() { + ForetellLookAtCardEffect() { super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); } @@ -190,7 +190,7 @@ public class ForetellAbility extends SpecialAction { } } - public class ForetellAddCostEffect extends ContinuousEffectImpl { + public static class ForetellAddCostEffect extends ContinuousEffectImpl { private final MageObjectReference mor; @@ -297,12 +297,12 @@ public class ForetellAbility extends SpecialAction { } } - public class ForetellCostAbility extends SpellAbility { + static class ForetellCostAbility extends SpellAbility { private String abilityName; private SpellAbility spellAbilityToResolve; - public ForetellCostAbility(String foretellCost) { + ForetellCostAbility(String foretellCost) { super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); // Needed for Dream Devourer and Ethereal Valkyrie reducing the cost of a colorless CMC 2 or less spell to 0 // CardUtil.reduceCost returns an empty string in that case so we add a cost of 0 here @@ -457,12 +457,15 @@ public class ForetellAbility extends SpecialAction { /** * Used for split card in PlayerImpl method: * getOtherUseableActivatedAbilities - * - * @param abilityName */ public void setAbilityName(String abilityName) { this.abilityName = abilityName; } } + + public static boolean isCardInForetell(Card card, Game game) { + // searching ForetellCostAbility - it adds for foretelled cards only after exile + return card.getAbilities(game).containsClass(ForetellCostAbility.class); + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/GraftAbility.java b/Mage/src/main/java/mage/abilities/keyword/GraftAbility.java index ce0ea36581a..3871c49ee02 100644 --- a/Mage/src/main/java/mage/abilities/keyword/GraftAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/GraftAbility.java @@ -142,7 +142,7 @@ class GraftDistributeCounterEffect extends OneShotEffect { if (sourcePermanent != null) { int numberOfCounters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); if (numberOfCounters > 0) { - Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { sourcePermanent.removeCounters(CounterType.P1P1.getName(), 1, source, game); targetCreature.addCounters(CounterType.P1P1.createInstance(1), source.getControllerId(), source, game); diff --git a/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java b/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java index 98dd328dedb..9e082ec4b65 100644 --- a/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/GravestormAbility.java @@ -3,8 +3,10 @@ package mage.abilities.keyword; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.ValueHint; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; @@ -21,6 +23,9 @@ public class GravestormAbility extends TriggeredAbilityImpl { public GravestormAbility() { super(Zone.STACK, new GravestormEffect()); this.addWatcher(new GravestormWatcher()); + this.addHint(new ValueHint( + "Permanents put into graveyards from the battlefield this turn", PermanentsDestroyedThisTurnValue.instance + )); } private GravestormAbility(final GravestormAbility ability) { @@ -95,3 +100,22 @@ class GravestormEffect extends OneShotEffect { return new GravestormEffect(this); } } + +enum PermanentsDestroyedThisTurnValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getState().getWatcher(GravestormWatcher.class).getGravestormCount(); + } + + @Override + public PermanentsDestroyedThisTurnValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java index 4bd7f4018bc..fb9124f495a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java @@ -180,7 +180,7 @@ class HauntEffect extends OneShotEffect { if (player == null || card == null) { return false; } - Permanent hauntedCreature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent hauntedCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); // haunting card will only be moved to exile, if if (hauntedCreature == null) { return false; @@ -193,7 +193,7 @@ class HauntEffect extends OneShotEffect { .append(source.getSourceId().toString()) .append(card.getZoneChangeCounter(game) + hauntedCreature.getZoneChangeCounter(game)).toString(); // in case it is blinked - game.getState().setValue(key, new FixedTarget(targetPointer.getFirst(game, source), game)); + game.getState().setValue(key, new FixedTarget(getTargetPointer().getFirst(game, source), game)); card.addInfo("hauntinfo", new StringBuilder("Haunting ").append(hauntedCreature.getLogName()).toString(), game); hauntedCreature.addInfo("hauntinfo", new StringBuilder("Haunted by ").append(card.getLogName()).toString(), game); game.informPlayers(new StringBuilder(card.getName()).append(" haunting ").append(hauntedCreature.getLogName()).toString()); diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromMulticoloredAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromMulticoloredAbility.java new file mode 100644 index 00000000000..e18812dbf4f --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromMulticoloredAbility.java @@ -0,0 +1,47 @@ +package mage.abilities.keyword; + +import java.io.ObjectStreamException; + +import mage.MageObject; +import mage.game.Game; + +public class HexproofFromMulticoloredAbility extends HexproofBaseAbility { + + private static final HexproofFromMulticoloredAbility instance; + + static { + instance = new HexproofFromMulticoloredAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static HexproofFromMulticoloredAbility getInstance() { + return instance; + } + + private HexproofFromMulticoloredAbility() { + super(); + } + + @Override + public HexproofFromMulticoloredAbility copy() { + return instance; + } + + @Override + public boolean checkObject(MageObject source, Game game) { + return source.getColor().isMulticolored(); + } + + @Override + public String getRule() { + return "hexproof from multicolored (This creature can't be the target of multicolored spells or abilities your opponents control.)"; + } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from multicolored"; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/LifelinkAbility.java b/Mage/src/main/java/mage/abilities/keyword/LifelinkAbility.java index d4949750087..2a709e28cba 100644 --- a/Mage/src/main/java/mage/abilities/keyword/LifelinkAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/LifelinkAbility.java @@ -33,7 +33,7 @@ public class LifelinkAbility extends StaticAbility implements MageSingleton { @Override public String getRule() { - return "lifelink (Damage dealt by this creature also causes you to gain that much life.)"; + return "lifelink"; } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java b/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java index 27deaf53740..7938ccdc3d7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java @@ -1,14 +1,17 @@ package mage.abilities.keyword; +import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.Card; +import mage.constants.Outcome; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.permanent.AttackingPredicate; import mage.game.Game; +import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; @@ -25,7 +28,7 @@ public class MentorAbility extends AttacksTriggeredAbility { } public MentorAbility() { - super(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false); + super(new MentorEffect(), false); this.addTarget(new TargetCreaturePermanent(filter)); } @@ -54,3 +57,37 @@ enum MentorAbilityPredicate implements ObjectSourcePlayerPredicate { return sourcePermanent != null && input.getObject().getPower().getValue() < sourcePermanent.getPower().getValue(); } } + +class MentorEffect extends AddCountersTargetEffect { + + MentorEffect() { + super(CounterType.P1P1.createInstance(), Outcome.BoostCreature); + } + + private MentorEffect(final MentorEffect effect) { + super(effect); + } + + @Override + public MentorEffect copy() { + return new MentorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!super.apply(game, source)) { + return false; + } + + Permanent mentoredPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (mentoredPermanent == null) { + return false; + } + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.MENTORED_CREATURE, + mentoredPermanent.getId(), + source, + source.getControllerId())); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ModularAbility.java b/Mage/src/main/java/mage/abilities/keyword/ModularAbility.java index 64467254580..30d4caf5fdd 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ModularAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ModularAbility.java @@ -150,7 +150,7 @@ class ModularDistributeCounterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent sourcePermanent = (Permanent) getValue("permanentLeftBattlefield"); - Permanent targetArtifact = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent targetArtifact = game.getPermanent(getTargetPointer().getFirst(game, source)); Player player = game.getPlayer(source.getControllerId()); if (sourcePermanent != null && targetArtifact != null && player != null) { int numberOfCounters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); diff --git a/Mage/src/main/java/mage/abilities/keyword/MorphAbility.java b/Mage/src/main/java/mage/abilities/keyword/MorphAbility.java index 3048b4a7482..85d0cddde70 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MorphAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MorphAbility.java @@ -1,7 +1,5 @@ package mage.abilities.keyword; -import mage.MageObject; -import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; @@ -13,18 +11,17 @@ import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType; import mage.cards.Card; -import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.permanent.token.EmptyToken; -import mage.game.permanent.token.Token; -import mage.util.CardUtil; +import mage.constants.SpellAbilityCastMode; +import mage.constants.SpellAbilityType; +import mage.constants.TimingRule; /** + * Morph and Megamorph + *

    * 702.36. Morph *

    * 702.36a Morph is a static ability that functions in any zone from which you - * could play the card it’s on, and the morph effect works any time the card is + * could play the card it's on, and the morph effect works any time the card is * face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down * creature, with no text, no name, no subtypes, and no mana cost by paying {3} * rather than paying its mana cost." (See rule 707, "Face-Down Spells and @@ -33,7 +30,7 @@ import mage.util.CardUtil; * 702.36b To cast a card using its morph ability, turn it face down. It becomes * a 2/2 face-down creature card, with no text, no name, no subtypes, and no * mana cost. Any effects or prohibitions that would apply to casting a card - * with these characteristics (and not the face-up card’s characteristics) are + * with these characteristics (and not the face-up card's characteristics) are * applied to casting this card. These values are the copiable values of that * object's characteristics. (See rule 613, "Interaction of Continuous Effects," * and rule 706, "Copying Objects.") Put it onto the stack (as a face-down spell @@ -48,54 +45,57 @@ import mage.util.CardUtil; *

    * 702.36d If you have priority, you may turn a face-down permanent you control * face up. This is a special action; it doesn't use the stack (see rule 115). - * To do this, show all players what the permanent’s morph cost would be if it + * To do this, show all players what the permanent's morph cost would be if it * were face up, pay that cost, then turn the permanent face up. (If the - * permanent wouldn't have a morph cost if it were face up, it can’t be turned + * permanent wouldn't have a morph cost if it were face up, it can't be turned * face up this way.) The morph effect on it ends, and it regains its normal * characteristics. Any abilities relating to the permanent entering the - * battlefield don’t trigger when it’s turned face up and don’t have any effect, + * battlefield don't trigger when it's turned face up and don't have any effect, * because the permanent has already entered the battlefield. *

    * 702.36e See rule 707, "Face-Down Spells and Permanents," for more information * on how to cast cards with morph. * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class MorphAbility extends SpellAbility { + protected static final String ABILITY_KEYWORD = "Morph"; - protected static final String ABILITY_KEYWORD_MEGA = "Megamorph"; protected static final String REMINDER_TEXT = "You may cast this card face down as a " + "2/2 creature for {3}. Turn it face up any time for its morph cost."; + + protected static final String ABILITY_KEYWORD_MEGA = "Megamorph"; protected static final String REMINDER_TEXT_MEGA = "You may cast this card face down " + "as a 2/2 creature for {3}. Turn it face up any time for its megamorph " + "cost and put a +1/+1 counter on it."; protected Costs morphCosts; - // needed to check activation status, if card changes zone after casting it - private final boolean megamorph; + protected boolean isMegamorph; public MorphAbility(Card card, Cost morphCost) { this(card, morphCost, false); } - public MorphAbility(Card card, Cost morphCost, boolean megamorph) { + public MorphAbility(Card card, Cost morphCost, boolean useMegamorph) { super(new GenericManaCost(3), card.getName()); + this.timing = TimingRule.SORCERY; this.morphCosts = new CostsImpl<>(); this.morphCosts.add(morphCost); - this.megamorph = megamorph; + this.isMegamorph = useMegamorph; this.setSpellAbilityCastMode(SpellAbilityCastMode.MORPH); this.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE); + + // face down effect (hidden by default, visible in face down objects) Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect( - morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED))); + this.morphCosts, (useMegamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED))); ability.setWorksFaceDown(true); ability.setRuleVisible(false); - this.timing = TimingRule.SORCERY; addSubAbility(ability); } protected MorphAbility(final MorphAbility ability) { super(ability); - this.morphCosts = ability.morphCosts; // can't be changed - this.megamorph = ability.megamorph; + this.morphCosts = ability.morphCosts; // can't be changed TODO: looks buggy, need research + this.isMegamorph = ability.isMegamorph; } @Override @@ -103,62 +103,22 @@ public class MorphAbility extends SpellAbility { return new MorphAbility(this); } - public Costs getMorphCosts() { - return morphCosts; + public Costs getFaceUpCosts() { + return this.morphCosts; } @Override public String getRule() { boolean isMana = morphCosts.get(0) instanceof ManaCost; - String name = megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD; - String reminder = " (" + (megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT) + ")"; - return name + (isMana ? " " : "—") + - morphCosts.getText() + (isMana ? ' ' : ". ") + reminder; - } - - /** - * Hide all info and make it a 2/2 creature - * - * @param targetObject - * @param sourcePermanent source of the face down status - * @param game - */ - public static void setPermanentToFaceDownCreature(MageObject targetObject, Permanent sourcePermanent, Game game) { - targetObject.getPower().setModifiedBaseValue(2); - targetObject.getToughness().setModifiedBaseValue(2); - targetObject.getAbilities().clear(); - targetObject.getColor(game).setColor(new ObjectColor()); - targetObject.setName(""); - targetObject.removeAllCardTypes(game); - targetObject.addCardType(game, CardType.CREATURE); - targetObject.removeAllSubTypes(game); - targetObject.removeAllSuperTypes(game); - targetObject.getManaCost().clear(); - - Token emptyImage = new EmptyToken(); - - // TODO: add morph image here? - if (targetObject instanceof Permanent) { - // hide image info - CardUtil.copySetAndCardNumber(targetObject, emptyImage); - // hide rarity info - ((Permanent) targetObject).setRarity(Rarity.SPECIAL); - } else if (targetObject instanceof Token) { - CardUtil.copySetAndCardNumber(targetObject, emptyImage); + String text; + String reminder; + if (isMegamorph){ + text = ABILITY_KEYWORD_MEGA; + reminder = REMINDER_TEXT_MEGA; } else { - throw new IllegalArgumentException("Wrong code usage: un-supported targetObject in face down method: " + targetObject.getClass().getSimpleName()); + text = ABILITY_KEYWORD; + reminder = REMINDER_TEXT; } - } - public static void setCardToFaceDownCreature(Card targetCard) { - targetCard.getPower().setModifiedBaseValue(2); - targetCard.getToughness().setModifiedBaseValue(2); - targetCard.getAbilities().clear(); - targetCard.getColor().setColor(new ObjectColor()); - targetCard.setName(""); - targetCard.removeAllCardTypes(); - targetCard.addCardType(CardType.CREATURE); - targetCard.getSubtype().clear(); - targetCard.removeAllSuperTypes(); - targetCard.getManaCost().clear(); + return text + (isMana ? " " : "—") + morphCosts.getText() + (isMana ? ' ' : ". ") + " (" + reminder + ")"; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/MyriadAbility.java b/Mage/src/main/java/mage/abilities/keyword/MyriadAbility.java index fe3d3b6f35c..18f9f20d736 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MyriadAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MyriadAbility.java @@ -89,7 +89,7 @@ class MyriadEffect extends OneShotEffect { } if (!tokens.isEmpty()) { ExileTargetEffect exileEffect = new ExileTargetEffect(); - exileEffect.setTargetPointer(new FixedTargets(tokens, game)); + exileEffect.setTargetPointer(new FixedTargets(new ArrayList<>(tokens), game)); game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect), source); } return true; diff --git a/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java index e9cab133d3b..f60258fbf2e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java @@ -80,27 +80,34 @@ public class ProtectionAbility extends StaticAbility { } public boolean canTarget(MageObject source, Game game) { + // TODO: need research, protection ability can be bugged with aura and aura permanents, spells (see below) + + // permanent restriction if (filter instanceof FilterPermanent) { if (source instanceof Permanent) { - return !filter.match(source, game); + return !((FilterPermanent) filter).match((Permanent) source, game); } + // TODO: possible bugged, need token too? return true; } + // card restriction if (filter instanceof FilterCard) { - if (source instanceof Permanent) { - return !((FilterCard) filter).match((Card) source, ((Permanent) source).getControllerId(), this, game); - } else if (source instanceof Card) { - return !((FilterCard) filter).match((Card) source, ((Card) source).getOwnerId(), this, game); + if (source instanceof Card) { + return !((FilterCard) filter).match((Card) source, ((Card) source).getControllerOrOwnerId(), this, game); } else if (source instanceof Token) { - // Fake a permanent with the Token info. - PermanentToken token = new PermanentToken((Token) source, null, game); - return !((FilterCard) filter).match((Card) token, game); + // make fake permanent cause it checked before real permanent create + // warning, Token don't have controllerId info, so it can be a problem here + // TODO: wtf, possible bugged for filters that checking controller/player (if so then use with controllerId param) + PermanentToken fakePermanent = new PermanentToken((Token) source, UUID.randomUUID(), game); + return !((FilterCard) filter).match(fakePermanent, game); } return true; } + // spell restriction if (filter instanceof FilterSpell) { + // TODO: need research, possible bugged // Problem here is that for the check if a player can play a Spell, the source // object is still a card and not a spell yet. if (source instanceof Spell || game.inCheckPlayableState() && source.isInstantOrSorcery(game)) { @@ -109,16 +116,20 @@ public class ProtectionAbility extends StaticAbility { return true; } + // unknown restriction if (filter instanceof FilterObject) { - return !filter.match(source, game); + return !((FilterObject) filter).match(source, game); } + // player restriction if (filter instanceof FilterPlayer) { Player player = null; - if (source instanceof Permanent) { - player = game.getPlayer(((Permanent) source).getControllerId()); - } else if (source instanceof Card) { - player = game.getPlayer(((Card) source).getOwnerId()); + if (source instanceof Card) { + player = game.getPlayer(((Card) source).getControllerOrOwnerId()); + } else if (source instanceof Token) { + // TODO: fakePermanent will not work here like above, so try to rework whole logic + throw new IllegalArgumentException("Wrong code usage: token can't be checked in player restriction filter"); + } return !((FilterPlayer) filter).match(player, this.getControllerId(), this, game); } diff --git a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java index 1285e549d80..4d7bd7f0dd6 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java @@ -77,7 +77,7 @@ class ProvokeRequirementEffect extends RequirementEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.getId().equals(targetPointer.getFirst(game, source)); + return permanent.getId().equals(getTargetPointer().getFirst(game, source)); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/ScavengeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ScavengeAbility.java index ab14a36558a..1af482c855a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ScavengeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ScavengeAbility.java @@ -72,7 +72,7 @@ class ScavengeEffect extends OneShotEffect { int count = card.getPower().getValue(); if (count > 0) { Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(count)); - effect.setTargetPointer(getTargetPointer()); + effect.setTargetPointer(this.getTargetPointer().copy()); return effect.apply(game, source); } } @@ -84,4 +84,4 @@ class ScavengeEffect extends OneShotEffect { public ScavengeEffect copy() { return new ScavengeEffect(this); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java index 95b2ac9113f..014b160ccaa 100644 --- a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java @@ -3,8 +3,10 @@ package mage.abilities.keyword; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.ValueHint; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; @@ -21,6 +23,7 @@ public class StormAbility extends TriggeredAbilityImpl { public StormAbility() { super(Zone.STACK, new StormEffect()); + this.addHint(new ValueHint("Spells cast this turn", SpellsCastThisTurnValue.instance)); } private StormAbility(final StormAbility ability) { @@ -97,3 +100,23 @@ class StormEffect extends OneShotEffect { return new StormEffect(this); } } + +enum SpellsCastThisTurnValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + CastSpellLastTurnWatcher watcher = game.getState().getWatcher(CastSpellLastTurnWatcher.class); + return watcher.getAmountOfSpellsCastOnCurrentTurn().values().stream().mapToInt(Integer::intValue).sum(); + } + + @Override + public SpellsCastThisTurnValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java index afaaa3baec5..ed6e2691fa3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java @@ -30,25 +30,25 @@ import java.util.List; import java.util.UUID; /** - * 502.59. Suspend + * 702.62. Suspend *

    - * 502.59a Suspend is a keyword that represents three abilities. The first is a - * static ability that functions while the card with suspend is in a player's - * hand. The second and third are triggered abilities that function in the - * removed-from-the-game zone. "Suspend N--[cost]" means "If you could play this - * card from your hand, you may pay [cost] and remove it from the game with N - * time counters on it. This is a special action that doesn't use the stack," - * and "At the beginning of your upkeep, if this card is suspended, remove a - * time counter from it," and "When the last time counter is removed from this - * card, if it's removed from the game, play it without paying its mana cost if - * able. If you can't, it remains removed from the game. If you play it this way - * and it's a creature, it gains haste until you lose control of it." + * 702.62a. Suspend is a keyword that represents three abilities. + * The first is a static ability that functions while the card with suspend is in a player's hand. + * The second and third are triggered abilities that function in the exile zone. + * "Suspend N--[cost]" means "If you could begin to cast this card by putting it onto the stack from your hand, + * you may pay [cost] and exile it with N time counters on it. This action doesn't use the stack," + * and "At the beginning of your upkeep, if this card is suspended, remove a time counter from it," + * and "When the last time counter is removed from this card, if it's exiled, + * you may play it without paying its mana cost if able. If you don't, it remains exiled. + * If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes." *

    - * 502.59b A card is "suspended" if it's in the removed-from-the-game zone, has - * suspend, and has a time counter on it. + * 702.62b. A card is "suspended" if it's in the exile zone, has suspend, and has a time counter on it. *

    - * 502.59c Playing a spell as an effect of its suspend ability follows the rules - * for paying alternative costs in rules 409.1b and 409.1f-h. + * 702.62c. While determining if you could begin to cast a card with suspend, + * take into consideration any effects that would prohibit that card from being cast. + *

    + * 702.62d. Casting a spell as an effect of its suspend ability follows the rules + * for paying alternative costs in rules 601.2b and 601.2f-h. *

    * The phrase "if you could play this card from your hand" checks only for * timing restrictions and permissions. This includes both what's inherent in @@ -124,7 +124,7 @@ public class SuspendAbility extends SpecialAction { this(suspend, cost, card, false); } - public SuspendAbility(int suspend, ManaCost cost, Card card, boolean shortRule) { + public SuspendAbility(int suspend, ManaCost cost, Card card, boolean hideReminderText) { super(Zone.HAND); this.addCost(cost); this.addEffect(new SuspendExileEffect(suspend)); @@ -135,39 +135,42 @@ public class SuspendAbility extends SpecialAction { this.addCost(xCosts); cost = new ManaCostsImpl<>("{X}" + cost.getText()); } - StringBuilder sb = new StringBuilder("Suspend "); if (cost != null) { - sb.append(suspend == Integer.MAX_VALUE ? "X" : suspend).append("—") - .append(cost.getText()).append(suspend - == Integer.MAX_VALUE ? ". X can't be 0." : ""); - if (!shortRule) { - sb.append(" (Rather than cast this card from your hand, you may pay ") - .append(cost.getText()) - .append(" and exile it with ") - .append((suspend == 1 ? "a time counter" : (suspend == Integer.MAX_VALUE - ? "X time counters" : CardUtil.numberToText(suspend) + " time counters"))) - .append(" on it.") - .append(" At the beginning of your upkeep, remove a time counter. " - + "When the last is removed, cast it without paying its mana cost.") - .append(card.isCreature() ? " It has haste." : "") - .append(")"); - } + ruleText = "Suspend " + (suspend == Integer.MAX_VALUE ? "X" : suspend) + "—" + + cost.getText() + (suspend == Integer.MAX_VALUE ? ". X can't be 0." : "") + + (hideReminderText ? "" : makeReminderText(suspend, cost.getText(), card.isCreature())); if (card.getManaCost().isEmpty()) { setRuleAtTheTop(true); } addSubAbility(new SuspendBeginningOfUpkeepInterveningIfTriggeredAbility()); addSubAbility(new SuspendPlayCardAbility()); + } else { + ruleText = "Suspend"; } - ruleText = sb.toString(); + } + + private String makeReminderText(int suspend, String costText, boolean isCreature) { + String counterText; + switch (suspend) { + case 1: + counterText = "a time counter"; + break; + case Integer.MAX_VALUE: + counterText = "X time counters"; + break; + default: + counterText = CardUtil.numberToText(suspend) + " time counters"; + } + return " (Rather than cast this card from your hand, you may pay " + costText + + " and exile it with " + counterText + " on it. " + + "At the beginning of your upkeep, remove a time counter. " + + "When the last is removed, you may cast it without paying its mana cost." + + (isCreature ? " It has haste." : "") + ")"; } /** * Adds suspend to a card that does not have it regularly e.g. Epochrasite * or added by Jhoira of the Ghitu - * - * @param card - * @param source - * @param game */ public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) { SuspendAbility ability = new SuspendAbility(0, null, card, false); @@ -196,9 +199,9 @@ public class SuspendAbility extends SpecialAction { return exileId; } - public SuspendAbility(SuspendAbility ability) { + private SuspendAbility(final SuspendAbility ability) { super(ability); - this.ruleText = ability.getRule(); + this.ruleText = ability.ruleText; this.gainedTemporary = ability.gainedTemporary; } @@ -239,14 +242,13 @@ class SuspendExileEffect extends OneShotEffect { private int suspend; - public SuspendExileEffect(int suspend) { + SuspendExileEffect(int suspend) { super(Outcome.PutCardInPlay); - this.staticText = new StringBuilder("Suspend ").append(suspend - == Integer.MAX_VALUE ? "X" : suspend).toString(); + this.staticText = "Suspend " + (suspend == Integer.MAX_VALUE ? "X" : suspend); this.suspend = suspend; } - protected SuspendExileEffect(final SuspendExileEffect effect) { + private SuspendExileEffect(final SuspendExileEffect effect) { super(effect); this.suspend = effect.suspend; } @@ -260,33 +262,31 @@ class SuspendExileEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Card card = game.getCard(source.getSourceId()); Player controller = game.getPlayer(source.getControllerId()); - if (card != null && controller != null) { - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " - + controller.getName(), source, game, Zone.HAND, true)) { - if (suspend == Integer.MAX_VALUE) { - suspend = source.getManaCostsToPay().getX(); - } - card.addCounters(CounterType.TIME.createInstance(suspend), source.getControllerId(), source, game); - if (!game.isSimulation()) { - game.informPlayers(controller.getLogName() - + " suspends (" + suspend + ") " + card.getLogName()); - } - return true; - } + if (card == null || controller == null) { + return false; } - return false; + UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); + if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " + + controller.getName(), source, game, Zone.HAND, true)) { + if (suspend == Integer.MAX_VALUE) { + suspend = source.getManaCostsToPay().getX(); + } + card.addCounters(CounterType.TIME.createInstance(suspend), source.getControllerId(), source, game); + game.informPlayers(controller.getLogName() + + " suspends (" + suspend + ") " + card.getLogName()); + } + return true; } } class SuspendPlayCardAbility extends TriggeredAbilityImpl { - public SuspendPlayCardAbility() { + SuspendPlayCardAbility() { super(Zone.EXILED, new SuspendPlayCardEffect()); setRuleVisible(false); } - public SuspendPlayCardAbility(SuspendPlayCardAbility ability) { + private SuspendPlayCardAbility(final SuspendPlayCardAbility ability) { super(ability); } @@ -308,8 +308,7 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "When the last time counter is removed from this card ({this}), " - + "if it's removed from the game, "; + return "When the last time counter is removed from {this}, if it's exiled, "; } @Override @@ -320,10 +319,9 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl { class SuspendPlayCardEffect extends OneShotEffect { - public SuspendPlayCardEffect() { + SuspendPlayCardEffect() { super(Outcome.PlayForFree); - this.staticText = "play it without paying its mana cost if able. " - + "If you can't, it remains removed from the game"; + staticText = "you may play it without paying its mana cost if able. If you don't, it remains exiled"; } protected SuspendPlayCardEffect(final SuspendPlayCardEffect effect) { @@ -339,49 +337,48 @@ class SuspendPlayCardEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Card card = game.getCard(source.getSourceId()); - if (player != null && card != null) { - // remove temporary suspend ability (used e.g. for Epochrasite) - // TODO: isGainedTemporary is not set or use in other places, so it can be deleted?! - List abilitiesToRemove = new ArrayList<>(); - for (Ability ability : card.getAbilities(game)) { - if (ability instanceof SuspendAbility) { - if (((SuspendAbility) ability).isGainedTemporary()) { - abilitiesToRemove.add(ability); - } - } - } - if (!abilitiesToRemove.isEmpty()) { - for (Ability ability : card.getAbilities(game)) { - if (ability instanceof SuspendBeginningOfUpkeepInterveningIfTriggeredAbility - || ability instanceof SuspendPlayCardAbility) { - abilitiesToRemove.add(ability); - } - } - // remove the abilities from the card - // TODO: will not work with Adventure Cards and another auto-generated abilities list - // TODO: is it work after blink or return to hand? - /* - bug example: - Epochrasite bug: It comes out of suspend, is cast and enters the battlefield. THEN if it's returned to - its owner's hand from battlefield, the bounced Epochrasite can't be cast for the rest of the game. - */ - card.getAbilities().removeAll(abilitiesToRemove); - } - // cast the card for free - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - Boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - if (cardWasCast) { - if (card.isCreature(game)) { - ContinuousEffect effect = new GainHasteEffect(); - effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1)); - game.addEffect(effect, source); - } - return true; + if (player == null || card == null) { + return false; + } + if (!player.chooseUse(Outcome.Benefit, "Play " + card.getLogName() + " without paying its mana cost?", source, game)) { + return true; + } + // remove temporary suspend ability (used e.g. for Epochrasite) + // TODO: isGainedTemporary is not set or use in other places, so it can be deleted?! + List abilitiesToRemove = new ArrayList<>(); + for (Ability ability : card.getAbilities(game)) { + if (ability instanceof SuspendAbility && (((SuspendAbility) ability).isGainedTemporary())) { + abilitiesToRemove.add(ability); } } - return false; + if (!abilitiesToRemove.isEmpty()) { + for (Ability ability : card.getAbilities(game)) { + if (ability instanceof SuspendBeginningOfUpkeepInterveningIfTriggeredAbility + || ability instanceof SuspendPlayCardAbility) { + abilitiesToRemove.add(ability); + } + } + // remove the abilities from the card + // TODO: will not work with Adventure Cards and another auto-generated abilities list + // TODO: is it work after blink or return to hand? + /* + bug example: + Epochrasite bug: It comes out of suspend, is cast and enters the battlefield. THEN if it's returned to + its owner's hand from battlefield, the bounced Epochrasite can't be cast for the rest of the game. + */ + card.getAbilities().removeAll(abilitiesToRemove); + } + // cast the card for free + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true), + game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + if (cardWasCast && (card.isCreature(game))) { + ContinuousEffect effect = new GainHasteEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1)); + game.addEffect(effect, source); + } + return true; } } @@ -389,12 +386,12 @@ class GainHasteEffect extends ContinuousEffectImpl { private UUID suspendController; - public GainHasteEffect() { + GainHasteEffect() { super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - staticText = "If you play it this way and it's a creature, it gains haste until you lose control of it"; + staticText = "If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes."; } - protected GainHasteEffect(final GainHasteEffect effect) { + private GainHasteEffect(final GainHasteEffect effect) { super(effect); this.suspendController = effect.suspendController; } @@ -429,11 +426,11 @@ class GainHasteEffect extends ContinuousEffectImpl { class SuspendBeginningOfUpkeepInterveningIfTriggeredAbility extends ConditionalInterveningIfTriggeredAbility { - public SuspendBeginningOfUpkeepInterveningIfTriggeredAbility() { + SuspendBeginningOfUpkeepInterveningIfTriggeredAbility() { super(new BeginningOfUpkeepTriggeredAbility(Zone.EXILED, new RemoveCounterSourceEffect(CounterType.TIME.createInstance()), TargetController.YOU, false), SuspendedCondition.instance, - "At the beginning of your upkeep, if this card ({this}) is suspended, remove a time counter from it."); + "At the beginning of your upkeep, if {this} is suspended, remove a time counter from it."); this.setRuleVisible(false); } diff --git a/Mage/src/main/java/mage/abilities/keyword/TrainingAbility.java b/Mage/src/main/java/mage/abilities/keyword/TrainingAbility.java index 0b1a9d4bed0..46e6f0fadeb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/TrainingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/TrainingAbility.java @@ -2,10 +2,8 @@ package mage.abilities.keyword; import mage.MageInt; import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; -import mage.constants.Outcome; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -20,7 +18,7 @@ import java.util.Objects; public class TrainingAbility extends TriggeredAbilityImpl { public TrainingAbility() { - super(Zone.BATTLEFIELD, new TrainingAbilityEffect()); + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); } private TrainingAbility(final TrainingAbility ability) { @@ -60,33 +58,3 @@ public class TrainingAbility extends TriggeredAbilityImpl { "with greater power, put a +1/+1 counter on this creature.)"; } } - -class TrainingAbilityEffect extends OneShotEffect { - - TrainingAbilityEffect() { - super(Outcome.Neutral); - } - - private TrainingAbilityEffect(final TrainingAbilityEffect effect) { - super(effect); - } - - @Override - public TrainingAbilityEffect copy() { - return new TrainingAbilityEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent == null) { - return false; - } - permanent.addCounters(CounterType.P1P1.createInstance(), source, game); - game.fireEvent(GameEvent.getEvent( - GameEvent.EventType.TRAINED_CREATURE, - source.getSourceId(), source, source.getControllerId() - )); - return true; - } -} diff --git a/Mage/src/main/java/mage/abilities/keyword/UnleashAbility.java b/Mage/src/main/java/mage/abilities/keyword/UnleashAbility.java index 77bf702e391..cb490fc9efb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/UnleashAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/UnleashAbility.java @@ -39,7 +39,7 @@ public class UnleashAbility extends SimpleStaticAbility { @Override public String getRule() { - return "Unleash (You may have this creature enter the battlefield with a +1/+1 counter on it. It can't block as long as it has a +1/+1 counter on it.)"; + return "unleash (You may have this creature enter the battlefield with a +1/+1 counter on it. It can't block as long as it has a +1/+1 counter on it.)"; } } diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 875e1095233..080ab85c040 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -9,6 +9,7 @@ import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; import java.util.List; import java.util.UUID; @@ -129,6 +130,12 @@ public abstract class AdventureCard extends CardImpl { return super.getAbilities(game); } + public List getSharedRules(Game game) { + // rules without spellcard + Abilities sourceAbilities = this.getSharedAbilities(game); + return CardUtil.getCardRulesWithAdditionalInfo(game, this.getId(), this.getName(), sourceAbilities, sourceAbilities); + } + @Override public void setOwnerId(UUID ownerId) { super.setOwnerId(ownerId); diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java index 414fe262aa7..2062b27ca9e 100644 --- a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java +++ b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java @@ -181,4 +181,4 @@ class AdventureCardSpellAbility extends SpellAbility { public AdventureCardSpellAbility copy() { return new AdventureCardSpellAbility(this); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/cards/ArtRect.java b/Mage/src/main/java/mage/cards/ArtRect.java index d4453c29ccc..fde58cc7c53 100644 --- a/Mage/src/main/java/mage/cards/ArtRect.java +++ b/Mage/src/main/java/mage/cards/ArtRect.java @@ -11,7 +11,9 @@ public enum ArtRect { AFTERMATH_BOTTOM(new Rectangle2D.Double(0.546, 0.562, 0.272, 0.346)), SPLIT_LEFT(new Rectangle2D.Double(0.152, 0.539, 0.386, 0.400)), SPLIT_RIGHT(new Rectangle2D.Double(0.152, 0.058, 0.386, 0.400)), - SPLIT_FUSED(null); + SPLIT_FUSED(null), + FULL_LENGTH_LEFT(new Rectangle2D.Double(0.069, 0.11, 0.426, 0.727)), + FULL_LENGTH_RIGHT(new Rectangle2D.Double(0.497, 0.11, 0.426, 0.727)); public final Rectangle2D rect; diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index a8712e04757..be08b51aa0e 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -14,6 +14,7 @@ import mage.counters.Counters; import mage.filter.FilterMana; import mage.game.Game; import mage.game.GameState; +import mage.game.Ownerable; import mage.game.permanent.Permanent; import mage.util.ManaUtil; import mage.watchers.common.CommanderPlaysCountWatcher; @@ -21,12 +22,12 @@ import mage.watchers.common.CommanderPlaysCountWatcher; import java.util.List; import java.util.UUID; -public interface Card extends MageObject { - - UUID getOwnerId(); +public interface Card extends MageObject, Ownerable { Rarity getRarity(); // null for tokens + void setRarity(Rarity rarity); + void setOwnerId(UUID ownerId); /** @@ -46,7 +47,11 @@ public interface Card extends MageObject { List getRules(Game game); // gets card rules + in game modifications - void checkForCountersToAdd(Permanent permanent, Ability source, Game game); + /** + * Find ETB counters and apply it to permanent. + * Warning, it's one time action, use it before a put to battlefield only. + */ + void applyEnterWithCounters(Permanent permanent, Ability source, Game game); void setFaceDown(boolean value, Game game); @@ -54,6 +59,7 @@ public interface Card extends MageObject { boolean turnFaceUp(Ability source, Game game, UUID playerId); + // TODO: need research, is it lost morph and other face down statuses? boolean turnFaceDown(Ability source, Game game, UUID playerId); boolean isFlipCard(); @@ -143,10 +149,12 @@ public interface Card extends MageObject { List getMana(); /** - * @return true if there exists various art images for this card + * Set contains multiple cards with same card name but different images. Used for image path generation. */ boolean getUsesVariousArt(); + void setUsesVariousArt(boolean usesVariousArt); + Counters getCounters(Game game); Counters getCounters(GameState state); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 4d4cde3bbf7..87c2305f867 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -11,6 +11,7 @@ import mage.abilities.keyword.FlashbackAbility; import mage.abilities.keyword.ReconfigureAbility; import mage.abilities.keyword.SunburstAbility; import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.cards.mock.MockableCard; import mage.cards.repository.PluginClassloaderRegistery; import mage.constants.*; import mage.counters.Counter; @@ -58,12 +59,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card { protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) { this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE); } + protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { this(ownerId, setInfo.getName()); this.rarity = setInfo.getRarity(); this.setExpansionSetCode(setInfo.getExpansionSetCode()); this.setCardNumber(setInfo.getCardNumber()); + this.setImageFileName(""); // use default + this.setImageNumber(0); this.cardType.addAll(Arrays.asList(cardTypes)); this.manaCost.load(costs); setDefaultColor(); @@ -119,12 +123,24 @@ public abstract class CardImpl extends MageObjectImpl implements Card { ownerId = card.ownerId; rarity = card.rarity; + // TODO: wtf, do not copy card sides cause it must be re-created each time (see details in getSecondCardFace) + // must be reworked to normal copy and workable transform without such magic + + nightCard = card.nightCard; secondSideCardClazz = card.secondSideCardClazz; secondSideCard = null; // will be set on first getSecondCardFace call if card has one - nightCard = card.nightCard; + if (card.secondSideCard instanceof MockableCard) { + // workaround to support gui's mock cards + secondSideCard = card.secondSideCard.copy(); + } + meldsWithClazz = card.meldsWithClazz; meldsToClazz = card.meldsToClazz; meldsToCard = null; // will be set on first getMeldsToCard call if card has one + if (card.meldsToCard instanceof MockableCard) { + // workaround to support gui's mock cards + meldsToCard = card.meldsToCard.copy(); + } spellAbility = null; // will be set on first getSpellAbility call if card has one flipCard = card.flipCard; @@ -203,6 +219,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return rarity; } + @Override + public void setRarity(Rarity rarity) { + this.rarity = rarity; + } + @Override public void addInfo(String key, String value, Game game) { game.getState().getCardState(objectId).addInfo(key, value); @@ -360,7 +381,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public void setOwnerId(UUID ownerId) { this.ownerId = ownerId; - abilities.setControllerId(ownerId); + this.abilities.setControllerId(ownerId); + } + + @Override + public UUID getControllerOrOwnerId() { + return getOwnerId(); } @Override @@ -549,7 +575,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } @Override - public void checkForCountersToAdd(Permanent permanent, Ability source, Game game) { + public void applyEnterWithCounters(Permanent permanent, Ability source, Game game) { Counters countersToAdd = game.getEnterWithCounters(permanent.getId()); if (countersToAdd != null) { for (Counter counter : countersToAdd.values()) { @@ -620,6 +646,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { if (secondSideCard == null) { secondSideCard = initSecondSideCard(secondSideCardClazz); if (secondSideCard != null && secondSideCard.getSpellAbility() != null) { + // TODO: wtf, why it set cast mode here?! Transform tests fails without it + // must be reworked without that magic, also see CardImpl'constructor for copy code secondSideCard.getSpellAbility().setSourceId(this.getId()); secondSideCard.getSpellAbility().setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE); secondSideCard.getSpellAbility().setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED); @@ -693,6 +721,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return usesVariousArt; } + @Override + public void setUsesVariousArt(boolean usesVariousArt) { + this.usesVariousArt = usesVariousArt; + } + @Override public Counters getCounters(Game game) { return getCounters(game.getState()); @@ -703,13 +736,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return state.getCardState(this.objectId).getCounters(); } - /** - * @return The controller if available otherwise the owner. - */ - protected UUID getControllerOrOwner() { - return ownerId; - } - @Override public boolean addCounters(Counter counter, Ability source, Game game) { return addCounters(counter, source.getControllerId(), source, game); @@ -787,7 +813,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { if (!getCounters(game).removeCounter(name, 1)) { break; } - GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, source, getControllerOrOwner()); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, source, getControllerOrOwnerId()); if (source != null && source.getControllerId() != null) { event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter @@ -796,7 +822,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { game.fireEvent(event); finalAmount++; } - GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, source, getControllerOrOwner()); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, source, getControllerOrOwnerId()); if (source != null && source.getControllerId() != null) { event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index dbba6605550..a9e8be08ee9 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -232,7 +232,7 @@ public abstract class ExpansionSet implements Serializable { } CardInfo cardInfo = cards.remove(RandomUtil.nextInt(cards.size())); - Card card = cardInfo.getCard(); + Card card = cardInfo.createCard(); if (card == null) { // card with error return; @@ -273,7 +273,7 @@ public abstract class ExpansionSet implements Serializable { .makeBooster() .stream() .map(inBoosterMap::get) - .map(CardInfo::getCard) + .map(CardInfo::createCard) .collect(Collectors.toList()); } @@ -569,7 +569,7 @@ public abstract class ExpansionSet implements Serializable { booster.forEach(card -> { List reprints = this.savedReprints.getOrDefault(card.getName(), null); if (reprints != null && reprints.size() > 1) { - Card newCard = reprints.get(RandomUtil.nextInt(reprints.size())).getCard(); + Card newCard = reprints.get(RandomUtil.nextInt(reprints.size())).createCard(); if (newCard != null) { finalBooster.add(newCard); return; diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java index dafe544bfb8..7e637f5635a 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java @@ -94,15 +94,29 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH @Override public void setCopy(boolean isCopy, MageObject copiedFrom) { super.setCopy(isCopy, copiedFrom); - leftHalfCard.setCopy(isCopy, copiedFrom); + leftHalfCard.setCopy(isCopy, copiedFrom); // TODO: must check copiedFrom and assign sides? (??? related to #8476 ???) rightHalfCard.setCopy(isCopy, copiedFrom); } + private void setSideZones(Zone mainZone, Game game) { + switch (mainZone) { + case BATTLEFIELD: + case STACK: + throw new IllegalArgumentException("Wrong code usage: you must put to battlefield/stack only real side card (half), not main"); + default: + // must keep both sides in same zone cause xmage need access to cost reduction, spell + // and other abilities before put it to stack (in playable calcs) + game.setZone(leftHalfCard.getId(), mainZone); + game.setZone(rightHalfCard.getId(), mainZone); + break; + } + checkGoodZones(game, this); + } + @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - game.getState().setZone(leftHalfCard.getId(), toZone); - game.getState().setZone(rightHalfCard.getId(), toZone); + setSideZones(toZone, game); return true; } return false; @@ -111,21 +125,69 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH @Override public void setZone(Zone zone, Game game) { super.setZone(zone, game); - game.setZone(leftHalfCard.getId(), zone); - game.setZone(rightHalfCard.getId(), zone); + setSideZones(zone, game); } @Override public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { if (super.moveToExile(exileId, name, source, game, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - game.getState().setZone(leftHalfCard.getId(), currentZone); - game.getState().setZone(rightHalfCard.getId(), currentZone); + setSideZones(Zone.EXILED, game); return true; } return false; } + /** + * Runtime check for good zones and other MDF data + */ + public static void checkGoodZones(Game game, ModalDoubleFacedCard card) { + Card leftPart = card.getLeftHalfCard(); + Card rightPart = card.getRightHalfCard(); + + Zone zoneMain = game.getState().getZone(card.getId()); + Zone zoneLeft = game.getState().getZone(leftPart.getId()); + Zone zoneRight = game.getState().getZone(rightPart.getId()); + + // runtime check: + // * in battlefield and stack - card + one of the sides (another side in outside zone) + // * in other zones - card + both sides (need both sides due cost reductions, spell and other access before put to stack) + // + // 712.8a While a double-faced card is outside the game or in a zone other than the battlefield or stack, + // it has only the characteristics of its front face. + // + // 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on the battlefield, + // it has only the characteristics of the face that’s up. + Zone needZoneLeft; + Zone needZoneRight; + switch (zoneMain) { + case BATTLEFIELD: + case STACK: + if (zoneMain == zoneLeft) { + needZoneLeft = zoneMain; + needZoneRight = Zone.OUTSIDE; + } else if (zoneMain == zoneRight) { + needZoneLeft = Zone.OUTSIDE; + needZoneRight = zoneMain; + } else { + // impossible + needZoneLeft = zoneMain; + needZoneRight = Zone.OUTSIDE; + } + break; + default: + needZoneLeft = zoneMain; + needZoneRight = zoneMain; + break; + } + + if (zoneLeft != needZoneLeft || zoneRight != needZoneRight) { + throw new IllegalStateException("Wrong code usage: MDF card uses wrong zones - " + card + + "\r\n" + String.format("* main zone: %s", zoneMain) + + "\r\n" + String.format("* left side: need %s, actual %s", needZoneLeft, zoneLeft) + + "\r\n" + String.format("* right side: need %s, actual %s", needZoneRight, zoneRight)); + } + } + @Override public boolean removeFromZone(Game game, Zone fromZone, Ability source) { // zone contains only one main card diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java b/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java index 917d2ecd590..2521b78aee8 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java @@ -71,9 +71,32 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl @Override public void setZone(Zone zone, Game game) { + // see ModalDoubleFacedCard.checkGoodZones for details game.setZone(parentCard.getId(), zone); - game.setZone(parentCard.getLeftHalfCard().getId(), zone); - game.setZone(parentCard.getRightHalfCard().getId(), zone); + game.setZone(this.getId(), zone); + + // find another side to sync + ModalDoubleFacedCardHalf otherSide; + if (!parentCard.getLeftHalfCard().getId().equals(this.getId())) { + otherSide = parentCard.getLeftHalfCard(); + } else if (!parentCard.getRightHalfCard().getId().equals(this.getId())) { + otherSide = parentCard.getRightHalfCard(); + } else { + throw new IllegalStateException("Wrong code usage: MDF halves must use different ids"); + } + + switch (zone) { + case STACK: + case BATTLEFIELD: + // stack and battlefield must have only one side + game.setZone(otherSide.getId(), Zone.OUTSIDE); + break; + default: + game.setZone(otherSide.getId(), zone); + break; + } + + ModalDoubleFacedCard.checkGoodZones(game, parentCard); } @Override diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 40943485f36..152d51e53bf 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -127,7 +127,7 @@ public class Sets extends HashMap { List cardPool = new ArrayList<>(); while (count < cardsCount) { CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); - Card card = cardInfo != null ? cardInfo.getCard() : null; + Card card = cardInfo != null ? cardInfo.createCard() : null; if (card != null) { FilterMana manaCard = card.getColorIdentity(); diff --git a/Mage/src/main/java/mage/cards/SplitCard.java b/Mage/src/main/java/mage/cards/SplitCard.java index 318778200c0..ae691f0d584 100644 --- a/Mage/src/main/java/mage/cards/SplitCard.java +++ b/Mage/src/main/java/mage/cards/SplitCard.java @@ -12,7 +12,6 @@ import mage.game.Game; import mage.game.events.ZoneChangeEvent; import mage.util.CardUtil; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -21,23 +20,23 @@ import java.util.UUID; */ public abstract class SplitCard extends CardImpl implements CardWithHalves { - static public String FUSE_RULE = "Fuse (You may cast both halves from your hand.)"; + public static final String FUSE_RULE = "Fuse (You may cast one or both halves of this card from your hand.)"; protected Card leftHalfCard; protected Card rightHalfCard; - public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costsLeft, String costsRight, SpellAbilityType spellAbilityType) { + protected SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costsLeft, String costsRight, SpellAbilityType spellAbilityType) { this(ownerId, setInfo, cardTypes, cardTypes, costsLeft, costsRight, spellAbilityType); } - public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] typesLeft, CardType[] typesRight, String costsLeft, String costsRight, SpellAbilityType spellAbilityType) { + protected SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] typesLeft, CardType[] typesRight, String costsLeft, String costsRight, SpellAbilityType spellAbilityType) { super(ownerId, setInfo, CardType.mergeTypes(typesLeft, typesRight), costsLeft + costsRight, spellAbilityType); String[] names = setInfo.getName().split(" // "); leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesLeft, costsLeft, this, SpellAbilityType.SPLIT_LEFT); rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesRight, costsRight, this, SpellAbilityType.SPLIT_RIGHT); } - public SplitCard(SplitCard card) { + protected SplitCard(SplitCard card) { super(card); this.leftHalfCard = card.getLeftHalfCard().copy(); ((SplitCardHalf) leftHalfCard).setParentCard(this); @@ -162,8 +161,6 @@ public abstract class SplitCard extends CardImpl implements CardWithHalves { * Currently only gets the fuse SpellAbility if there is one, but generally * gets any abilities on a split card as a whole, and not on either half * individually. - * - * @return */ public Abilities getSharedAbilities(Game game) { return super.getAbilities(game); diff --git a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java index 5aeaef50fd4..eced59de623 100644 --- a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java +++ b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java @@ -19,6 +19,7 @@ public class CardNameUtil { .replace("ö", "o") .replace("û", "u") .replace("í", "i") + .replace("ï", "i") .replace("â", "a") .replace("á", "a") .replace("à", "a") diff --git a/Mage/src/main/java/mage/cards/decks/Deck.java b/Mage/src/main/java/mage/cards/decks/Deck.java index a5ff5fb02f4..a68343a35d6 100644 --- a/Mage/src/main/java/mage/cards/decks/Deck.java +++ b/Mage/src/main/java/mage/cards/decks/Deck.java @@ -184,9 +184,9 @@ public class Deck implements Serializable, Copyable { } if (mockCards) { - return cardInfo.getMockCard(); + return cardInfo.createMockCard(); } else { - return cardInfo.getCard(); + return cardInfo.createCard(); } } diff --git a/Mage/src/main/java/mage/cards/decks/DeckValidator.java b/Mage/src/main/java/mage/cards/decks/DeckValidator.java index fccc650698c..c4cee146e87 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckValidator.java +++ b/Mage/src/main/java/mage/cards/decks/DeckValidator.java @@ -22,7 +22,8 @@ public abstract class DeckValidator implements Serializable { "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", - "Snow-Covered Forest" + "Snow-Covered Forest", + "Snow-Covered Wastes" ); protected static final Map maxCopiesMap = new HashMap<>(); @@ -34,6 +35,7 @@ public abstract class DeckValidator implements Serializable { maxCopiesMap.put("Persistent Petitioners", Integer.MAX_VALUE); maxCopiesMap.put("Dragon's Approach", Integer.MAX_VALUE); maxCopiesMap.put("Slime Against Humanity", Integer.MAX_VALUE); + maxCopiesMap.put("Once More with Feeling", 1); maxCopiesMap.put("Seven Dwarves", 7); maxCopiesMap.put("Nazgul", 9); } diff --git a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java index 57bc486956d..6372ecaf2f6 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java @@ -9,7 +9,7 @@ import java.util.Scanner; public abstract class DeckImporter { - public class FixedInfo { + public static class FixedInfo { private final String originalLine; private String fixedLine; private Boolean canFix = true; // set false if deck have critical error and can't be auto-fixed diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index 7361b6cc04e..5ca0affe4e0 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -18,7 +18,7 @@ import java.util.List; * * @author North */ -public class MockCard extends CardImpl { +public class MockCard extends CardImpl implements MockableCard { static public String ADVENTURE_NAME_SEPARATOR = " // "; static public String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // "; @@ -42,6 +42,8 @@ public class MockCard extends CardImpl { super(null, card.getName()); this.setExpansionSetCode(card.getSetCode()); this.setCardNumber(card.getCardNumber()); + this.setImageFileName(""); // use default + this.setImageNumber(0); this.power = mageIntFromString(card.getPower()); this.toughness = mageIntFromString(card.getToughness()); this.rarity = card.getRarity(); @@ -75,7 +77,7 @@ public class MockCard extends CardImpl { } if (card.isModalDoubleFacedCard()) { - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card.getCard(); + ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card.createCard(); CardInfo mdfSecondSide = new CardInfo(mdfCard.getRightHalfCard()); this.secondSideCard = new MockCard(mdfSecondSide); this.isModalDoubleFacedCard = true; @@ -157,7 +159,7 @@ public class MockCard extends CardImpl { if (adventureSpellName != null) { return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName; } else if (isModalDoubleFacedCard) { - return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.secondSideCard.getName(); + return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName(); } else { return getName(); } diff --git a/Mage/src/main/java/mage/cards/mock/MockSplitCard.java b/Mage/src/main/java/mage/cards/mock/MockSplitCard.java index 605667b7b8e..cc8d961e2dd 100644 --- a/Mage/src/main/java/mage/cards/mock/MockSplitCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockSplitCard.java @@ -18,7 +18,8 @@ import java.util.List; /** * @author North */ -public class MockSplitCard extends SplitCard { +public class MockSplitCard extends SplitCard implements MockableCard { + public MockSplitCard(CardInfo card) { super(null, new CardSetInfo(card.getName(), card.getSetCode(), card.getCardNumber(), card.getRarity()), card.getTypes().toArray(new CardType[0]), diff --git a/Mage/src/main/java/mage/cards/mock/MockableCard.java b/Mage/src/main/java/mage/cards/mock/MockableCard.java new file mode 100644 index 00000000000..2cba82f73f6 --- /dev/null +++ b/Mage/src/main/java/mage/cards/mock/MockableCard.java @@ -0,0 +1,9 @@ +package mage.cards.mock; + +/** + * Card is mock, e.g. used in GUI only like deck editor + * + * @author JayDi85 + */ +public interface MockableCard { +} diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index e838b1cf252..4876768cdd7 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -238,11 +238,17 @@ public class CardInfo { this.isExtraDeckCard = card.isExtraDeckCard(); } - public Card getCard() { + /** + * Create normal card (with full abilities) + */ + public Card createCard() { return CardImpl.createCard(className, new CardSetInfo(name, setCode, cardNumber, rarity, new CardGraphicInfo(FrameStyle.valueOf(frameStyle), variousArt))); } - public Card getMockCard() { + /** + * Create deck editor's mock card (with text only instead real abilities) + */ + public Card createMockCard() { if (this.splitCard) { return new MockSplitCard(this); } else { diff --git a/Mage/src/main/java/mage/cards/repository/TokenInfo.java b/Mage/src/main/java/mage/cards/repository/TokenInfo.java index 4488bbedda7..105ce9359bb 100644 --- a/Mage/src/main/java/mage/cards/repository/TokenInfo.java +++ b/Mage/src/main/java/mage/cards/repository/TokenInfo.java @@ -14,21 +14,18 @@ public class TokenInfo { private final String classFileName; - private final String imageFileName; - private String downloadUrl = ""; public TokenInfo(TokenType tokenType, String name, String setCode, Integer imageNumber) { - this(tokenType, name, setCode, imageNumber, "", ""); + this(tokenType, name, setCode, imageNumber, ""); } - public TokenInfo(TokenType tokenType, String name, String setCode, Integer imageNumber, String classFileName, String imageFileName) { + public TokenInfo(TokenType tokenType, String name, String setCode, Integer imageNumber, String classFileName) { this.tokenType = tokenType; this.name = name; this.setCode = setCode; this.imageNumber = imageNumber; this.classFileName = classFileName; - this.imageFileName = imageFileName; } @Override @@ -44,10 +41,6 @@ public class TokenInfo { return name; } - public String getImageFileName() { - return imageFileName; - } - public String getSetCode() { return setCode; } diff --git a/Mage/src/main/java/mage/cards/repository/TokenRepository.java b/Mage/src/main/java/mage/cards/repository/TokenRepository.java index 370cf1b3b6f..24d3ac76966 100644 --- a/Mage/src/main/java/mage/cards/repository/TokenRepository.java +++ b/Mage/src/main/java/mage/cards/repository/TokenRepository.java @@ -18,6 +18,20 @@ public enum TokenRepository { public static final String XMAGE_TOKENS_SET_CODE = "XMAGE"; + // All possible image names. Used for: + // - image name from tok/xmage folder + // - additional card name for controller like "Morph: face up name" + public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL = "Face Down"; + public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST = "Manifest"; + public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MORPH = "Morph"; + public static final String XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE = "Disguise"; + public static final String XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL = "Foretell"; + public static final String XMAGE_IMAGE_NAME_COPY = "Copy"; + public static final String XMAGE_IMAGE_NAME_CITY_BLESSING = "City's Blessing"; + public static final String XMAGE_IMAGE_NAME_DAY = "Day"; + public static final String XMAGE_IMAGE_NAME_NIGHT = "Night"; + public static final String XMAGE_IMAGE_NAME_THE_MONARCH = "The Monarch"; + private static final Logger logger = Logger.getLogger(TokenRepository.class); private ArrayList allTokens = new ArrayList<>(); @@ -117,12 +131,6 @@ public enum TokenRepository { imageNumber = Integer.parseInt(params.get(4)); } - // image file name - String imageFileName = ""; - if (params.size() > 5 && !params.get(5).isEmpty()) { - imageFileName = params.get(5); - } - // token class name (uses for images search for render) String tokenClassName = ""; if (params.size() > 7 && !params.get(6).isEmpty()) { @@ -190,7 +198,7 @@ public enum TokenRepository { } // OK - TokenInfo token = new TokenInfo(tokenType, tokenName, setCode, imageNumber, tokenClassName, imageFileName); + TokenInfo token = new TokenInfo(tokenType, tokenName, setCode, imageNumber, tokenClassName); list.add(token); } finally { line = reader.readLine(); @@ -235,44 +243,58 @@ public enum TokenRepository { // Search by // - https://tagger.scryfall.com/tags/card/assistant-cards // - https://scryfall.com/search?q=otag%3Aassistant-cards&unique=cards&as=grid&order=name - // Must add only unique prints + // Must add only unique images/prints // TODO: add custom set in download window to download a custom tokens only - // TODO: add custom set in card viewer to view a custom tokens only ArrayList res = new ArrayList<>(); + // Backface + // TODO: can't find backface's api url so use direct link from third party site instead (must be replaced to scryfall someday) + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL, 1, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg")); + // Copy // https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+type%3Atoken+copy&unique=cards&as=grid&order=name - res.add(createXmageToken("Copy", 1, "https://api.scryfall.com/cards/tclb/19/en?format=image")); - res.add(createXmageToken("Copy", 2, "https://api.scryfall.com/cards/tsnc/1/en?format=image")); - res.add(createXmageToken("Copy", 3, "https://api.scryfall.com/cards/tvow/19/en?format=image")); - res.add(createXmageToken("Copy", 4, "https://api.scryfall.com/cards/tznr/12/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 1, "https://api.scryfall.com/cards/tclb/19/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 2, "https://api.scryfall.com/cards/tsnc/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 3, "https://api.scryfall.com/cards/tvow/19/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 4, "https://api.scryfall.com/cards/tznr/12/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 5, "https://api.scryfall.com/cards/twho/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 6, "https://api.scryfall.com/cards/tlci/1/en?format=image")); // City's Blessing // https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name - res.add(createXmageToken("City's Blessing", 1, "https://api.scryfall.com/cards/f18/2/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_CITY_BLESSING, 1, "https://api.scryfall.com/cards/f18/2/en?format=image")); // Day // Night // https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+%22Day+%2F%2F+Night%22&unique=cards&as=grid&order=name - res.add(createXmageToken("Day", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=front")); - res.add(createXmageToken("Night", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=back")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_DAY, 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=front")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_NIGHT, 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=back")); // Manifest // https://scryfall.com/search?q=Manifest+include%3Aextras+unique%3Aprints&unique=cards&as=grid&order=name - res.add(createXmageToken("Manifest", 1, "https://api.scryfall.com/cards/tc19/28/en?format=image")); - res.add(createXmageToken("Manifest", 2, "https://api.scryfall.com/cards/tc18/1/en?format=image")); - res.add(createXmageToken("Manifest", 3, "https://api.scryfall.com/cards/tfrf/4/en?format=image")); - res.add(createXmageToken("Manifest", 4, "https://api.scryfall.com/cards/tncc/3/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 1, "https://api.scryfall.com/cards/tc19/28/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 2, "https://api.scryfall.com/cards/tc18/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 3, "https://api.scryfall.com/cards/tfrf/4/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 4, "https://api.scryfall.com/cards/tncc/3/en?format=image")); - // Morph + // Morph and Megamorph // https://scryfall.com/search?q=Morph+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name - res.add(createXmageToken("Morph", 1, "https://api.scryfall.com/cards/tktk/11/en?format=image")); - res.add(createXmageToken("Morph", 2, "https://api.scryfall.com/cards/ta25/15/en?format=image")); - res.add(createXmageToken("Morph", 3, "https://api.scryfall.com/cards/tc19/27/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 1, "https://api.scryfall.com/cards/tktk/11/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 2, "https://api.scryfall.com/cards/ta25/15/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 3, "https://api.scryfall.com/cards/tc19/27/en?format=image")); + + // Disguise + // support only 1 image: https://scryfall.com/card/tmkm/21/a-mysterious-creature + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_DISGUISE, 1, "https://api.scryfall.com/cards/tmkm/21/en?format=image")); + + // Foretell + // https://scryfall.com/search?q=Foretell+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name + res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, 1, "https://api.scryfall.com/cards/tkhm/23/en?format=image")); // The Monarch // https://scryfall.com/search?q=Monarch+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name - res.add(createXmageToken("The Monarch", 1, "https://api.scryfall.com/cards/tonc/22/en?format=image")); - res.add(createXmageToken("The Monarch", 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 1, "https://api.scryfall.com/cards/tonc/22/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 3, "https://api.scryfall.com/cards/tltc/15/en?format=image")); return res; } @@ -313,4 +335,33 @@ public enum TokenRepository { public TokenInfo findPreferredTokenInfoForClass(String className, String preferredSetCode) { return findPreferredTokenInfo(TokenRepository.instance.getByClassName(className), preferredSetCode); } + + /** + * Try to find random image info by related set code (use for inner tokens like copy, morph, etc) + *

    + * Allow to generate "random" image number from an object's UUID (workaround to keep same image after each update) + * + * @param randomFromId object's UUID for image number generation + */ + public TokenInfo findPreferredTokenInfoForXmage(String name, UUID randomFromId) { + List needList = TokenRepository.instance.getByType(TokenType.XMAGE) + .stream() + .filter(info -> info.getName().equals(name)) + .collect(Collectors.toList()); + if (needList.isEmpty()) { + return null; + } + if (needList.size() == 1) { + return needList.get(0); + } + + // workaround to find stable image from object's id (need for face down image generation) + if (randomFromId == null) { + return RandomUtil.randomFromCollection(needList); + } else { + // warning, do not use global random here (it can break it with same seed) + int itemIndex = new Random(randomFromId.getLeastSignificantBits()).nextInt(needList.size()); + return needList.get(itemIndex); + } + } } diff --git a/Mage/src/main/java/mage/constants/EmptyNames.java b/Mage/src/main/java/mage/constants/EmptyNames.java index 998e75714a2..96696c533c5 100644 --- a/Mage/src/main/java/mage/constants/EmptyNames.java +++ b/Mage/src/main/java/mage/constants/EmptyNames.java @@ -9,7 +9,10 @@ public enum EmptyNames { // TODO: make names for that cards and enable Assert.assertNotEquals("", permanentName); for assertXXX tests // TODO: replace all getName().equals to haveSameNames and haveEmptyName FACE_DOWN_CREATURE(""), // "Face down creature" - FACE_DOWN_TOKEN(""); // "Face down token" + FACE_DOWN_TOKEN(""), // "Face down token" + FACE_DOWN_CARD(""); // "Face down card" + + public static final String EMPTY_NAME_IN_LOGS = "face down object"; private final String cardName; @@ -21,4 +24,10 @@ public enum EmptyNames { public String toString() { return cardName; } + + public static boolean isEmptyName(String objectName) { + return objectName.equals(FACE_DOWN_CREATURE.toString()) + || objectName.equals(FACE_DOWN_TOKEN.toString()) + || objectName.equals(FACE_DOWN_CARD.toString()); + } } diff --git a/Mage/src/main/java/mage/constants/MageObjectType.java b/Mage/src/main/java/mage/constants/MageObjectType.java index 0a486ec4ce6..f0ff79b9671 100644 --- a/Mage/src/main/java/mage/constants/MageObjectType.java +++ b/Mage/src/main/java/mage/constants/MageObjectType.java @@ -43,27 +43,29 @@ package mage.constants; * @author LevelX2 */ public enum MageObjectType { - ABILITY_STACK("Ability on the Stack", false, false), - CARD("Card", false, true), - COPY_CARD("Copy of a Card", false, true), - TOKEN("Token", true, true), - SPELL("Spell", false, true), - PERMANENT("Permanent", true, true), - DUNGEON("Dungeon", false, false), - EMBLEM("Emblem", false, false), - COMMANDER("Commander", false, false), - DESIGNATION("Designation", false, false), - PLANE("Plane", false, false), - NULL("NullObject", false, false); + ABILITY_STACK("Ability on the Stack", false, false, false), + CARD("Card", false, true, false), + COPY_CARD("Copy of a Card", false, true, false), + TOKEN("Token", true, true, true), + SPELL("Spell", false, true, false), + PERMANENT("Permanent", true, true, false), + DUNGEON("Dungeon", false, false, true), + EMBLEM("Emblem", false, false, true), + COMMANDER("Commander", false, false, true), + DESIGNATION("Designation", false, false, true), + PLANE("Plane", false, false, true), + NULL("NullObject", false, false, false); private final String text; private final boolean permanent; private final boolean canHaveCounters; + private final boolean useTokensRepository; // card image from tokens or cards repository - MageObjectType(String text, boolean permanent, boolean canHaveCounters) { + MageObjectType(String text, boolean permanent, boolean canHaveCounters, boolean useTokensRepository) { this.text = text; this.permanent = permanent; this.canHaveCounters = canHaveCounters; + this.useTokensRepository = useTokensRepository; } @Override @@ -79,4 +81,8 @@ public enum MageObjectType { return canHaveCounters; } + public boolean isUseTokensRepository() { + return useTokensRepository; + } + } diff --git a/Mage/src/main/java/mage/constants/MatchTimeLimit.java b/Mage/src/main/java/mage/constants/MatchTimeLimit.java index 267508bb9e6..0cc7a3c8814 100644 --- a/Mage/src/main/java/mage/constants/MatchTimeLimit.java +++ b/Mage/src/main/java/mage/constants/MatchTimeLimit.java @@ -2,12 +2,13 @@ package mage.constants; /** * The time per player to have activity in a match. - * If time runs out for a player, they loose the currently running game of a match. + * If time runs out for a player, they lose the match. * * @author LevelX2 */ public enum MatchTimeLimit { NONE(0), + MIN___5(300), MIN__10(600), MIN__15(900), MIN__20(1200), @@ -33,10 +34,10 @@ public enum MatchTimeLimit { } public String getName() { - if (this == NONE){ + if (this == NONE) { return "None"; } else { - return (prioritySecs/60)+" Minutes"; + return (prioritySecs/60) + " Minutes"; } } @@ -46,7 +47,7 @@ public enum MatchTimeLimit { } public String getShortName() { - if (this == NONE){ + if (this == NONE) { return "None"; } else { return (prioritySecs/60) + "m"; diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index e4bc9b723bf..2c73fb3c439 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -1,10 +1,11 @@ package mage.constants; import mage.abilities.SpellAbility; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.PrototypeAbility; import mage.cards.Card; -import mage.abilities.keyword.MorphAbility; +import mage.game.Game; import mage.game.stack.Spell; /** @@ -16,16 +17,19 @@ public enum SpellAbilityCastMode { FLASHBACK("Flashback"), BESTOW("Bestow"), PROTOTYPE("Prototype"), - MORPH("Morph"), + MORPH("Morph", false, true), // and megamorph + DISGUISE("Disguise", false, true), TRANSFORMED("Transformed", true), DISTURB("Disturb", true), MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true); private final String text; - // Should the cast mode use the second face? + // should the cast mode use the second face? private final boolean isTransformed; + private final boolean isFaceDown; + public boolean isTransformed() { return this.isTransformed; } @@ -35,8 +39,17 @@ public enum SpellAbilityCastMode { } SpellAbilityCastMode(String text, boolean isTransformed) { + this(text, isTransformed, false); + } + + SpellAbilityCastMode(String text, boolean isTransformed, boolean isFaceDown) { this.text = text; this.isTransformed = isTransformed; + this.isFaceDown = isFaceDown; + } + + public boolean isFaceDown() { + return this.isFaceDown; } @Override @@ -44,27 +57,51 @@ public enum SpellAbilityCastMode { return text; } - public Card getTypeModifiedCardObjectCopy(Card card, SpellAbility spellAbility) { + public Card getTypeModifiedCardObjectCopy(Card card, SpellAbility spellAbility, Game game) { Card cardCopy = card.copy(); - if (this.equals(BESTOW)) { - BestowAbility.becomeAura(cardCopy); - } if (this.isTransformed) { Card tmp = card.getSecondCardFace(); if (tmp != null) { cardCopy = tmp.copy(); } } - if (this.equals(PROTOTYPE)) { - cardCopy = ((PrototypeAbility) spellAbility).prototypeCardSpell(cardCopy); - } - if (this.equals(MORPH)) { - if (cardCopy instanceof Spell) { - //Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway) - cardCopy = ((Spell) cardCopy).getCard().copy(); - } - MorphAbility.setCardToFaceDownCreature(cardCopy); + + switch (this) { + case BESTOW: + BestowAbility.becomeAura(cardCopy); + break; + case PROTOTYPE: + cardCopy = ((PrototypeAbility) spellAbility).prototypeCardSpell(cardCopy); + break; + case MORPH: + case DISGUISE: + if (cardCopy instanceof Spell) { + //Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway) + // TODO: research - is it possible to apply face down code to spell instead workaround with card + cardCopy = ((Spell) cardCopy).getCard().copy(); + } + BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.FaceDownType.MORPHED; + if (this == DISGUISE) { + faceDownType = BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED; + } + // no needs in additional abilities for spell + BecomesFaceDownCreatureEffect.makeFaceDownObject(game, null, cardCopy, faceDownType, null); + break; + case NORMAL: + case MADNESS: + case FLASHBACK: + case DISTURB: + case MORE_THAN_MEETS_THE_EYE: + // it changes only cost, so keep other characteristics + // TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?! + break; + case TRANSFORMED: + // TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?! + break; + default: + throw new IllegalArgumentException("Un-supported ability cast mode: " + this); } + return cardCopy; } } diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 7ac1f312714..73e412a22f5 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -57,6 +57,7 @@ public enum SubType { FORTIFICATION("Fortification", SubTypeSet.ArtifactType), GOLD("Gold", SubTypeSet.ArtifactType), INCUBATOR("Incubator", SubTypeSet.ArtifactType), + JUNK("Junk", SubTypeSet.ArtifactType), MAP("Map", SubTypeSet.ArtifactType), POWERSTONE("Powerstone", SubTypeSet.ArtifactType), TREASURE("Treasure", SubTypeSet.ArtifactType), @@ -359,6 +360,7 @@ public enum SubType { SKELETON("Skeleton", SubTypeSet.CreatureType), SLITH("Slith", SubTypeSet.CreatureType), SLIVER("Sliver", SubTypeSet.CreatureType), + SLOTH("Sloth", SubTypeSet.CreatureType), SLUG("Slug", SubTypeSet.CreatureType), SNAIL("Snail", SubTypeSet.CreatureType), SNAKE("Snake", SubTypeSet.CreatureType), @@ -463,6 +465,7 @@ public enum SubType { GRIST("Grist", SubTypeSet.PlaneswalkerType), GUFF("Guff", SubTypeSet.PlaneswalkerType), HUATLI("Huatli", SubTypeSet.PlaneswalkerType), + INZERVA("Inzerva", SubTypeSet.PlaneswalkerType), JACE("Jace", SubTypeSet.PlaneswalkerType), JARED("Jared", SubTypeSet.PlaneswalkerType), JESKA("Jeska", SubTypeSet.PlaneswalkerType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 1201f6f5d9d..c509013a28b 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -26,6 +26,7 @@ public enum CounterType { BLESSING("blessing"), BLOOD("blood"), BLOODLINE("bloodline"), + BLOODSTAIN("bloodstain"), BOOK("book"), BORE("bore"), BOUNTY("bounty"), @@ -56,6 +57,7 @@ public enum CounterType { DESCENT("descent"), DESPAIR("despair"), DEVOTION("devotion"), + DISCOVERY("discovery"), DIVINITY("divinity"), DOOM("doom"), DOUBLE_STRIKE("double strike"), @@ -103,6 +105,7 @@ public enum CounterType { HOURGLASS("hourglass", "an"), HUNGER("hunger"), ICE("ice"), + IMPOSTOR("impostor"), INCARNATION("incarnation"), INDESTRUCTIBLE("indestructible"), INFECTION("infection"), @@ -206,6 +209,7 @@ public enum CounterType { TRAP("trap"), TREASURE("treasure"), UNITY("unity", "a"), + UNLOCK("unlock"), VALOR("valor"), VELOCITY("velocity"), VERSE("verse"), diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index a0117b181f6..87c6b5af9a5 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -139,6 +139,12 @@ public final class StaticFilters { FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD.setLockedFilter(true); } + public static final FilterCreatureCard FILTER_CARD_CREATURE_A_GRAVEYARD = new FilterCreatureCard("creature card from a graveyard"); + + static { + FILTER_CARD_CREATURE_A_GRAVEYARD.setLockedFilter(true); + } + public static final FilterNoncreatureCard FILTER_CARD_NON_CREATURE = new FilterNoncreatureCard(); static { @@ -1230,4 +1236,10 @@ public final class StaticFilters { FILTER_CONTROLLED_FOOD.setLockedFilter(true); } + public static final FilterControlledPermanent FILTER_CONTROLLED_CLUE = new FilterControlledPermanent(SubType.CLUE, "a Clue"); + + static { + FILTER_CONTROLLED_CLUE.setLockedFilter(true); + } + } diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java index 6ab2fb1cf4e..1dec36f6875 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java @@ -1,32 +1,3 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.filter.common; import java.util.UUID; diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/NoAbilityPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/NoAbilityPredicate.java index fe450dc5a35..4fa84f22cd0 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/NoAbilityPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/NoAbilityPredicate.java @@ -8,6 +8,7 @@ import mage.abilities.keyword.special.JohanVigilanceAbility; import mage.cards.Card; import mage.filter.predicate.Predicate; import mage.game.Game; +import mage.util.CardUtil; import java.util.Objects; @@ -43,11 +44,18 @@ public enum NoAbilityPredicate implements Predicate { // (2007-05-01) for (Ability ability : abilities) { + // ignore inner face down abilities like turn up and becomes creature if (ability.getWorksFaceDown()) { - // inner face down abilities like turn up and becomes creature continue; } - if (!Objects.equals(ability.getClass(), SpellAbility.class) && !ability.getClass().equals(JohanVigilanceAbility.class)) { + + // ignore information abilities + if (CardUtil.isInformationAbility(ability)) { + continue; + } + + if (!Objects.equals(ability.getClass(), SpellAbility.class) + && !ability.getClass().equals(JohanVigilanceAbility.class)) { return false; } } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java deleted file mode 100644 index b1ee4511e2d..00000000000 --- a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java +++ /dev/null @@ -1,25 +0,0 @@ - -package mage.filter.predicate.permanent; - -import java.util.List; -import mage.filter.predicate.Predicate; -import mage.game.Game; -import mage.game.permanent.Permanent; - -/** - * - * @author L_J - */ -public class PermanentInListPredicate implements Predicate { - - private final List permanents; - - public PermanentInListPredicate(List permanents) { - this.permanents = permanents; - } - - @Override - public boolean apply(Permanent input, Game game) { - return (permanents.contains(input)); - } -} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java index cf80e66c40b..920fb75c90f 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java @@ -21,7 +21,9 @@ public class PermanentReferenceInCollectionPredicate implements Predicate permanents, Game game) { - this.references = permanents.stream().map((p) -> new MageObjectReference(p, game)) + this.references = permanents + .stream() + .map(p -> new MageObjectReference(p, game)) .collect(Collectors.toSet()); } diff --git a/Mage/src/main/java/mage/game/Controllable.java b/Mage/src/main/java/mage/game/Controllable.java index 5ab40c4ed9c..e5d639e293a 100644 --- a/Mage/src/main/java/mage/game/Controllable.java +++ b/Mage/src/main/java/mage/game/Controllable.java @@ -5,12 +5,10 @@ import java.util.UUID; /** * @author magenoxx_at_gmail.com */ -public interface Controllable { +public interface Controllable extends ControllableOrOwnerable { UUID getControllerId(); - UUID getId(); - default boolean isControlledBy(UUID controllerID) { if (getControllerId() == null) { return false; diff --git a/Mage/src/main/java/mage/game/ControllableOrOwnerable.java b/Mage/src/main/java/mage/game/ControllableOrOwnerable.java new file mode 100644 index 00000000000..a7841e67e04 --- /dev/null +++ b/Mage/src/main/java/mage/game/ControllableOrOwnerable.java @@ -0,0 +1,16 @@ +package mage.game; + +import mage.MageItem; + +import java.util.UUID; + +/** + * @author JayDi85 + */ +public interface ControllableOrOwnerable extends MageItem { + + /** + * @return the controller if available otherwise the owner + */ + UUID getControllerOrOwnerId(); +} diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index b929018b73b..7a749c14bfb 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -503,6 +503,7 @@ public interface Game extends MageItem, Serializable, Copyable { */ void applyEffects(); + @Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead) boolean checkStateAndTriggered(); void playPriority(UUID activePlayerId, boolean resuming); @@ -555,7 +556,7 @@ public interface Game extends MageItem, Serializable, Copyable { // game cheats (for tests only) void cheat(UUID ownerId, Map commands); - void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command); + void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command, List exiled); // controlling the behaviour of replacement effects while permanents entering the battlefield void setScopeRelevant(boolean scopeRelevant); @@ -589,6 +590,9 @@ public interface Game extends MageItem, Serializable, Copyable { boolean executingRollback(); + /** + * Add counters to permanent before ETB. Use it before put real permanent to battlefield. + */ void setEnterWithCounters(UUID sourceId, Counters counters); Counters getEnterWithCounters(UUID sourceId); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index ac8c5d80f70..0a5829f6773 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -15,6 +15,7 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.PreventionEffectData; import mage.abilities.effects.common.CopyEffect; import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.keyword.FinalityCounterEffect; import mage.abilities.effects.keyword.ShieldCounterEffect; import mage.abilities.effects.keyword.StunCounterEffect; @@ -288,6 +289,8 @@ public abstract class GameImpl implements Game { public void loadCards(Set cards, UUID ownerId) { for (Card card : cards) { if (card instanceof PermanentCard) { + // TODO: impossible use case, can be deleted? + // trying to put permanent card to battlefield card = ((PermanentCard) card).getCard(); } @@ -1994,10 +1997,9 @@ public abstract class GameImpl implements Game { // workaround to find real copyable characteristics of transformed/facedown/etc permanents - if (copyFromPermanent.isMorphed() - || copyFromPermanent.isManifested() - || copyFromPermanent.isFaceDown(this)) { - MorphAbility.setPermanentToFaceDownCreature(newBluePrint, copyFromPermanent, this); + BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.findFaceDownType(this, copyFromPermanent); + if (faceDownType != null) { + BecomesFaceDownCreatureEffect.makeFaceDownObject(this, null, newBluePrint, faceDownType, null); } newBluePrint.assignNewId(); if (copyFromPermanent.isTransformed()) { @@ -2019,11 +2021,10 @@ public abstract class GameImpl implements Game { // save original copy link (handle copy of copies too) newBluePrint.setCopy(true, (copyFromPermanent.getCopyFrom() != null ? copyFromPermanent.getCopyFrom() : copyFromPermanent)); - CopyEffect newEffect = new CopyEffect(duration, newBluePrint, copyToPermanentId); - newEffect.newId(); - newEffect.setApplier(applier); + CopyEffect newCopyEffect = new CopyEffect(duration, newBluePrint, copyToPermanentId); + newCopyEffect.setApplier(applier); Ability newAbility = source.copy(); - newEffect.init(newAbility, this); + newCopyEffect.init(newAbility, this); // If there are already copy effects with duration = Custom to the same object, remove the existing effects because they no longer have any effect if (duration == Duration.Custom) { @@ -2037,7 +2038,7 @@ public abstract class GameImpl implements Game { } } } - state.addEffect(newEffect, newAbility); + state.addEffect(newCopyEffect, newAbility); return newBluePrint; } @@ -3567,18 +3568,26 @@ public abstract class GameImpl implements Game { } @Override - public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command) { + public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command, List exiled) { // fake test ability for triggers and events Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); fakeSourceAbilityTemplate.setControllerId(ownerId); Player player = getPlayer(ownerId); if (player != null) { + // init cards loadCards(ownerId, library); loadCards(ownerId, hand); - loadCards(ownerId, battlefield); + loadCards(ownerId, battlefield + .stream() + .map(PutToBattlefieldInfo::getCard) + .collect(Collectors.toList()) + ); loadCards(ownerId, graveyard); loadCards(ownerId, command); + loadCards(ownerId, exiled); + + // move cards to zones for (Card card : library) { player.getLibrary().putOnTop(card, this); @@ -3604,10 +3613,15 @@ public abstract class GameImpl implements Game { throw new IllegalArgumentException("Command zone supports in commander test games"); } - for (PermanentCard permanentCard : battlefield) { + for (Card card : exiled) { + card.setZone(Zone.EXILED, this); + getExile().add(card); + } + + for (PutToBattlefieldInfo info : battlefield) { Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); - fakeSourceAbility.setSourceId(permanentCard.getId()); - CardUtil.putCardOntoBattlefieldWithEffects(fakeSourceAbility, this, permanentCard, player); + fakeSourceAbility.setSourceId(info.getCard().getId()); + CardUtil.putCardOntoBattlefieldWithEffects(fakeSourceAbility, this, info.getCard(), player, info.isTapped()); } applyEffects(); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index c4c3b17a6cf..21158cc1c07 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -812,33 +812,48 @@ public class GameState implements Serializable, Copyable { // Combine multiple damage events in the single event (batch) // * per damage type (see GameEvent.DAMAGED_BATCH_FOR_PERMANENTS, GameEvent.DAMAGED_BATCH_FOR_PLAYERS) // * per player (see GameEvent.DAMAGED_BATCH_FOR_ONE_PLAYER) + // * per permanent (see GameEvent.DAMAGED_BATCH_FOR_ONE_PERMANENT) // // Warning, one event can be stored in multiple batches, // example: DAMAGED_BATCH_FOR_PLAYERS + DAMAGED_BATCH_FOR_ONE_PLAYER boolean isPlayerDamage = damagedEvent instanceof DamagedPlayerEvent; + boolean isPermanentDamage = damagedEvent instanceof DamagedPermanentEvent; // existing batch boolean isDamageBatchUsed = false; boolean isPlayerBatchUsed = false; + boolean isPermanentBatchUsed = false; for (GameEvent event : simultaneousEvents) { - // per damage type - if ((event instanceof DamagedBatchEvent) - && ((DamagedBatchEvent) event).getDamageClazz().isInstance(damagedEvent)) { - ((DamagedBatchEvent) event).addEvent(damagedEvent); - isDamageBatchUsed = true; - } - - // per player if (isPlayerDamage && event instanceof DamagedBatchForOnePlayerEvent) { + // per player DamagedBatchForOnePlayerEvent oldPlayerBatch = (DamagedBatchForOnePlayerEvent) event; if (oldPlayerBatch.getDamageClazz().isInstance(damagedEvent) && event.getPlayerId().equals(damagedEvent.getTargetId())) { oldPlayerBatch.addEvent(damagedEvent); isPlayerBatchUsed = true; } + } else if (isPermanentDamage && event instanceof DamagedBatchForOnePermanentEvent) { + // per permanent + DamagedBatchForOnePermanentEvent oldPermanentBatch = (DamagedBatchForOnePermanentEvent) event; + if (oldPermanentBatch.getDamageClazz().isInstance(damagedEvent) + && CardUtil.getEventTargets(event).contains(damagedEvent.getTargetId())) { + oldPermanentBatch.addEvent(damagedEvent); + isPermanentBatchUsed = true; + } + } else if ((event instanceof DamagedBatchEvent) + && ((DamagedBatchEvent) event).getDamageClazz().isInstance(damagedEvent)) { + // per damage type + // If the batch event isn't DAMAGED_BATCH_FOR_ONE_PLAYER, the targetIDs need not match, + // since "event" is a generic batch in this case + // (either DAMAGED_BATCH_FOR_PERMANENTS or DAMAGED_BATCH_FOR_PLAYERS) + // Just needs to be a permanent-damaging event for DAMAGED_BATCH_FOR_PERMANENTS, + // or a player-damaging event for DAMAGED_BATCH_FOR_PLAYERS + ((DamagedBatchEvent) event).addEvent(damagedEvent); + isDamageBatchUsed = true; } + } // new batch @@ -846,8 +861,11 @@ public class GameState implements Serializable, Copyable { addSimultaneousEvent(DamagedBatchEvent.makeEvent(damagedEvent), game); } if (!isPlayerBatchUsed && isPlayerDamage) { - DamagedBatchEvent event = new DamagedBatchForOnePlayerEvent(damagedEvent.getTargetId()); - event.addEvent(damagedEvent); + DamagedBatchEvent event = new DamagedBatchForOnePlayerEvent(damagedEvent); + addSimultaneousEvent(event, game); + } + if (!isPermanentBatchUsed && isPermanentDamage) { + DamagedBatchEvent event = new DamagedBatchForOnePermanentEvent(damagedEvent); addSimultaneousEvent(event, game); } } @@ -952,9 +970,11 @@ public class GameState implements Serializable, Copyable { Map> eventsByKey = new HashMap<>(); List groupEvents = new LinkedList<>(); + ZoneChangeBatchEvent batchEvent = new ZoneChangeBatchEvent(); for (GameEvent event : events) { if (event instanceof ZoneChangeEvent) { ZoneChangeEvent castEvent = (ZoneChangeEvent) event; + batchEvent.addEvent(castEvent); ZoneChangeData key = new ZoneChangeData( castEvent.getSource(), castEvent.getSourceId(), @@ -999,6 +1019,9 @@ public class GameState implements Serializable, Copyable { groupEvents.add(event); } } + if (!batchEvent.getEvents().isEmpty()) { + groupEvents.add(batchEvent); + } return groupEvents; } diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index 13dd37646cb..1e76c1986d6 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -121,7 +121,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl { default: CardInfo cardInfo = CardRepository.instance.findCard(commanderName); if (cardInfo != null) { - commander = cardInfo.getCard(); + commander = cardInfo.createCard(); } } } diff --git a/Mage/src/main/java/mage/game/Ownerable.java b/Mage/src/main/java/mage/game/Ownerable.java new file mode 100644 index 00000000000..bac098cdcd7 --- /dev/null +++ b/Mage/src/main/java/mage/game/Ownerable.java @@ -0,0 +1,10 @@ +package mage.game; + +import java.util.UUID; + +/** + * @author JayDi85 + */ +public interface Ownerable extends ControllableOrOwnerable { + UUID getOwnerId(); +} diff --git a/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java b/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java new file mode 100644 index 00000000000..404cde602e6 --- /dev/null +++ b/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java @@ -0,0 +1,27 @@ +package mage.game; + +import mage.cards.Card; + +/** + * For tests only: put to battlefield with additional settings like tapped + * + * @author JayDi85 + */ +public class PutToBattlefieldInfo { + + private final Card card; + private final boolean tapped; + + public PutToBattlefieldInfo(Card card, boolean tapped) { + this.card = card; + this.tapped = tapped; + } + + public Card getCard() { + return card; + } + + public boolean isTapped() { + return tapped; + } +} diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 96c78f0d90c..d741e831cf9 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -1,7 +1,6 @@ package mage.game; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.*; import mage.constants.Outcome; @@ -27,7 +26,7 @@ public final class ZonesHandler { public static boolean cast(ZoneChangeInfo info, Ability source, Game game) { if (maybeRemoveFromSourceZone(info, game, source)) { - placeInDestinationZone(info,0, source, game); + placeInDestinationZone(info, 0, source, game); // create a group zone change event if a card is moved to stack for casting (it's always only one card, but some effects check for group events (one or more xxx)) Set cards = new HashSet<>(); Set tokens = new HashSet<>(); @@ -38,12 +37,12 @@ public final class ZonesHandler { cards.add(targetCard); } game.fireEvent(new ZoneChangeGroupEvent( - cards, - tokens, - info.event.getSourceId(), - info.event.getSource(), - info.event.getPlayerId(), - info.event.getFromZone(), + cards, + tokens, + info.event.getSourceId(), + info.event.getSource(), + info.event.getPlayerId(), + info.event.getFromZone(), info.event.getToZone())); // normal movement game.fireEvent(info.event); @@ -325,33 +324,50 @@ public final class ZonesHandler { // Handle all normal cases Card card = getTargetCard(game, event.getTargetId()); if (card == null) { - // If we can't find the card we can't remove it. + // if we can't find the card we can't remove it. return false; } - boolean success = false; + boolean isGoodToMove = false; if (info.faceDown) { - card.setFaceDown(true, game); + // any card can be moved as face down (doubled faced cards also support face down) + isGoodToMove = true; } else if (event.getToZone().equals(Zone.BATTLEFIELD)) { - if (!card.isPermanent(game) - && (!card.isTransformable() || Boolean.FALSE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())))) { - // Non permanents (Instants, Sorceries, ... stay in the zone they are if an abilty/effect tries to move it to the battlefield - return false; - } + // non-permanents can't move to battlefield + // "return to battlefield transformed" abilities uses game state value instead "info.transformed", so check it too + // TODO: possible bug with non permanent on second side like Life // Death, see https://github.com/magefree/mage/issues/11573 + // need to check second side here, not status only + // TODO: possible bug with Nightbound, search all usage of getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED and insert additional check Ability.checkCard + boolean wantToPutTransformed = card.isTransformable() + && Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())); + isGoodToMove = card.isPermanent(game) || wantToPutTransformed; + } else { + // other zones allows to move + isGoodToMove = true; + } + if (!isGoodToMove) { + return false; } + // TODO: is it buggy? Card characteristics are global - if you change face down then it will be + // changed in original card too, not in blueprint only + card.setFaceDown(info.faceDown, game); + + boolean success = false; if (!game.replaceEvent(event)) { Zone fromZone = event.getFromZone(); if (event.getToZone() == Zone.BATTLEFIELD) { - // prepare card and permanent - // If needed take attributes from the spell (e.g. color of spell was changed) - card = takeAttributesFromSpell(card, event, game); + // PUT TO BATTLEFIELD AS PERMANENT + // prepare card and permanent (card must contain full data, even for face down) + // if needed to take attributes from the spell (e.g. color of spell was changed) + card = prepareBlueprintCardFromSpell(card, event, game); + // controlling player can be replaced so use event player now Permanent permanent; if (card instanceof MeldCard) { permanent = new PermanentMeld(card, event.getPlayerId(), game); } else if (card instanceof ModalDoubleFacedCard) { - // main mdf card must be processed before that call (e.g. only halfes can be moved to battlefield) + // main mdf card must be processed before that call (e.g. only halves can be moved to battlefield) throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half"); } else if (card instanceof Permanent) { throw new IllegalStateException("Unexpected trying of move permanent to battlefield instead card"); @@ -361,11 +377,12 @@ public final class ZonesHandler { // put onto battlefield with possible counters game.getPermanentsEntering().put(permanent.getId(), permanent); - card.checkForCountersToAdd(permanent, source, game); + card.applyEnterWithCounters(permanent, source, game); permanent.setTapped(info instanceof ZoneChangeInfo.Battlefield && ((ZoneChangeInfo.Battlefield) info).tapped); - + + // if need prototyped version if (Zone.STACK == event.getFromZone()) { Spell spell = game.getStack().getSpell(event.getTargetId()); if (spell != null) { @@ -375,29 +392,34 @@ public final class ZonesHandler { permanent.setFaceDown(info.faceDown, game); if (info.faceDown) { - card.setFaceDown(false, game); + // TODO: need research cards with "setFaceDown(false" + // TODO: delete after new release and new face down bugs (old code remove face down status from a card for unknown reason), 2024-02-20 + //card.setFaceDown(false, game); } // make sure the controller of all continuous effects of this card are switched to the current controller game.setScopeRelevant(true); - game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); - if (permanent.entersBattlefield(source, game, fromZone, true) - && card.removeFromZone(game, fromZone, source)) { - success = true; - event.setTarget(permanent); - } else { - // revert controller to owner if permanent does not enter - game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId()); - game.getPermanentsEntering().remove(permanent.getId()); + try { + game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); + if (permanent.entersBattlefield(source, game, fromZone, true) + && card.removeFromZone(game, fromZone, source)) { + success = true; + event.setTarget(permanent); + } else { + // revert controller to owner if permanent does not enter + game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId()); + game.getPermanentsEntering().remove(permanent.getId()); + } + } finally { + game.setScopeRelevant(false); } - game.setScopeRelevant(false); } else if (event.getTarget() != null) { - card.setFaceDown(info.faceDown, game); + // PUT PERMANENT TO OTHER ZONE (e.g. remove only) Permanent target = event.getTarget(); success = target.removeFromZone(game, fromZone, source) && game.getPlayer(target.getControllerId()).removeFromBattlefield(target, source, game); } else { - card.setFaceDown(info.faceDown, game); + // PUT CARD TO OTHER ZONE success = card.removeFromZone(game, fromZone, source); } } @@ -434,16 +456,30 @@ public final class ZonesHandler { return order; } - private static Card takeAttributesFromSpell(Card card, ZoneChangeEvent event, Game game) { + private static Card prepareBlueprintCardFromSpell(Card card, ZoneChangeEvent event, Game game) { card = card.copy(); if (Zone.STACK == event.getFromZone()) { + // TODO: wtf, why only colors!? Must research and remove colors workaround or add all other data like types too Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell != null && !spell.isFaceDown(game)) { + + // old version + if (false && spell != null && !spell.isFaceDown(game)) { if (!card.getColor(game).equals(spell.getColor(game))) { // the card that is referenced to in the permanent is copied and the spell attributes are set to this copied card card.getColor(game).setColor(spell.getColor(game)); } } + + // new version + if (true && spell != null && spell.getSpellAbility() != null) { + Card characteristics = spell.getSpellAbility().getCharacteristics(game); + if (!characteristics.isFaceDown(game)) { + if (!card.getColor(game).equals(characteristics.getColor(game))) { + // TODO: don't work with prototyped spells (setColor can't set colorless color) + card.getColor(game).setColor(characteristics.getColor(game)); + } + } + } } return card; } diff --git a/Mage/src/main/java/mage/game/command/CommandObjectImpl.java b/Mage/src/main/java/mage/game/command/CommandObjectImpl.java index 87c81758702..e796f1ef1d3 100644 --- a/Mage/src/main/java/mage/game/command/CommandObjectImpl.java +++ b/Mage/src/main/java/mage/game/command/CommandObjectImpl.java @@ -13,8 +13,9 @@ public abstract class CommandObjectImpl implements CommandObject { private UUID id; private String name = ""; - private String expansionSetCode; - private String cardNumber; + private String expansionSetCode = ""; + private String cardNumber = ""; + private String imageFileName = ""; private int imageNumber; public CommandObjectImpl(String name) { @@ -27,6 +28,7 @@ public abstract class CommandObjectImpl implements CommandObject { this.name = object.name; this.expansionSetCode = object.expansionSetCode; this.cardNumber = object.cardNumber; + this.imageFileName = object.imageFileName; this.imageNumber = object.imageNumber; } @@ -55,6 +57,16 @@ public abstract class CommandObjectImpl implements CommandObject { this.cardNumber = cardNumber; } + @Override + public String getImageFileName() { + return imageFileName; + } + + @Override + public void setImageFileName(String imageFileName) { + this.imageFileName = imageFileName; + } + @Override public Integer getImageNumber() { return imageNumber; diff --git a/Mage/src/main/java/mage/game/command/Commander.java b/Mage/src/main/java/mage/game/command/Commander.java index e1c3ad6cf99..a124394f927 100644 --- a/Mage/src/main/java/mage/game/command/Commander.java +++ b/Mage/src/main/java/mage/game/command/Commander.java @@ -127,6 +127,11 @@ public class Commander extends CommandObjectImpl { return sourceObject.getOwnerId(); } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override public CommandObject copy() { return new Commander(this); diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index 9361f89f276..2b1e65b422c 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -184,6 +184,11 @@ public class Dungeon extends CommandObjectImpl { this.abilites.setControllerId(controllerId); } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override public void setCopy(boolean isCopy, MageObject copyFrom) { this.copy = isCopy; diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index cdb326ef113..cdd76315611 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -69,6 +69,7 @@ public abstract class Emblem extends CommandObjectImpl { if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); this.setCardNumber(""); + this.setImageFileName(""); // use default this.setImageNumber(foundInfo.getImageNumber()); } else { // how-to fix: add emblem to the tokens-database @@ -99,6 +100,11 @@ public abstract class Emblem extends CommandObjectImpl { this.abilites.setControllerId(controllerId); } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override abstract public Emblem copy(); diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java index 730ea503f49..b64b63721dc 100644 --- a/Mage/src/main/java/mage/game/command/Plane.java +++ b/Mage/src/main/java/mage/game/command/Plane.java @@ -73,6 +73,7 @@ public abstract class Plane extends CommandObjectImpl { if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); this.setCardNumber(""); + this.setImageFileName(""); // use default this.setImageNumber(foundInfo.getImageNumber()); } else { // how-to fix: add plane to the tokens-database @@ -103,6 +104,11 @@ public abstract class Plane extends CommandObjectImpl { this.abilites.setControllerId(controllerId); } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override abstract public Plane copy(); diff --git a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java index 03b8936e70b..f56faea246d 100644 --- a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java @@ -118,7 +118,7 @@ class DackFaydenEmblemTriggeredAbility extends TriggeredAbilityImpl { class DackFaydenEmblemEffect extends ContinuousEffectImpl { - protected FixedTargets fixedTargets; + protected FixedTargets fixedTargets = new FixedTargets(new ArrayList<>()); DackFaydenEmblemEffect() { super(Duration.EndOfGame, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); @@ -127,7 +127,7 @@ class DackFaydenEmblemEffect extends ContinuousEffectImpl { DackFaydenEmblemEffect(final DackFaydenEmblemEffect effect) { super(effect); - this.fixedTargets = effect.fixedTargets; + this.fixedTargets = effect.fixedTargets.copy(); } @Override @@ -147,6 +147,6 @@ class DackFaydenEmblemEffect extends ContinuousEffectImpl { } public void setTargets(List targetedPermanents, Game game) { - this.fixedTargets = new FixedTargets(targetedPermanents, game); + this.fixedTargets = new FixedTargets(new ArrayList<>(targetedPermanents), game); } } diff --git a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java index 74c16dbb2e2..d889e7278dd 100644 --- a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java +++ b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java @@ -23,8 +23,8 @@ import java.util.stream.Collectors; * mana burn with Yurlok of Scorch Thrash, and anything else players might think of. */ public final class EmblemOfCard extends Emblem { + private final boolean usesVariousArt; - private static final Logger logger = Logger.getLogger(EmblemOfCard.class); public static Card lookupCard( String cardName, @@ -44,7 +44,7 @@ public final class EmblemOfCard extends Emblem { .orElseGet(() -> found.stream() .findFirst() .orElseThrow(() -> new IllegalArgumentException("No real card for " + infoTypeForError + " " + cardName))) - .getCard(); + .createCard(); } public static Card cardFromDeckInfo(DeckCardInfo info) { @@ -75,8 +75,10 @@ public final class EmblemOfCard extends Emblem { return ability; }).collect(Collectors.toList())); this.getAbilities().setSourceId(this.getId()); + this.setExpansionSetCode(card.getExpansionSetCode()); this.setCardNumber(card.getCardNumber()); + this.setImageFileName(card.getImageFileName()); this.setImageNumber(card.getImageNumber()); this.usesVariousArt = card.getUsesVariousArt(); } diff --git a/Mage/src/main/java/mage/game/command/emblems/InzervaMasterOfInsightsEmblem.java b/Mage/src/main/java/mage/game/command/emblems/InzervaMasterOfInsightsEmblem.java new file mode 100644 index 00000000000..cf3f474532d --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/InzervaMasterOfInsightsEmblem.java @@ -0,0 +1,39 @@ +package mage.game.command.emblems; + +import mage.abilities.common.DrawCardOpponentTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.PlayWithHandRevealedEffect; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.command.Emblem; + +/** + * @author PurpleCrowbar + */ +public final class InzervaMasterOfInsightsEmblem extends Emblem { + + // You get an emblem with "Your opponents play with their hands revealed" and "Whenever an opponent draws a card, this emblem deals 1 damage to them." + public InzervaMasterOfInsightsEmblem() { + super("Emblem Inzerva"); + // Your opponents play with their hands revealed + this.getAbilities().add(new SimpleStaticAbility( + Zone.COMMAND, + new PlayWithHandRevealedEffect(TargetController.OPPONENT) + )); + // Whenever an opponent draws a card, this emblem deals 1 damage to them + this.getAbilities().add(new DrawCardOpponentTriggeredAbility( + Zone.COMMAND, new DamageTargetEffect(1, true, "them") + .setText("this emblem deals 1 damage to them"), false, true + )); + } + + private InzervaMasterOfInsightsEmblem(final InzervaMasterOfInsightsEmblem card) { + super(card); + } + + @Override + public InzervaMasterOfInsightsEmblem copy() { + return new InzervaMasterOfInsightsEmblem(this); + } +} diff --git a/Mage/src/main/java/mage/game/command/emblems/LilianaTheLastHopeEmblem.java b/Mage/src/main/java/mage/game/command/emblems/LilianaTheLastHopeEmblem.java index d181d83719d..b968fee6d4c 100644 --- a/Mage/src/main/java/mage/game/command/emblems/LilianaTheLastHopeEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/LilianaTheLastHopeEmblem.java @@ -8,7 +8,7 @@ import mage.abilities.effects.common.CreateTokenEffect; import mage.constants.SubType; import mage.constants.TargetController; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.command.Emblem; import mage.game.permanent.token.ZombieToken; @@ -21,7 +21,7 @@ public final class LilianaTheLastHopeEmblem extends Emblem { // "At the beginning of your end step, create X 2/2 black Zombie creature tokens, where X is two plus the number of Zombies you control." public LilianaTheLastHopeEmblem() { super("Emblem Liliana"); - Ability ability = new BeginningOfEndStepTriggeredAbility(Zone.COMMAND, new CreateTokenEffect(new ZombieToken(), new LilianaZombiesCount()), + Ability ability = new BeginningOfEndStepTriggeredAbility(Zone.COMMAND, new CreateTokenEffect(new ZombieToken(), LilianaZombiesCount.instance), TargetController.YOU, null, false); this.getAbilities().add(ability); } @@ -36,23 +36,19 @@ public final class LilianaTheLastHopeEmblem extends Emblem { } } -class LilianaZombiesCount implements DynamicValue { +enum LilianaZombiesCount implements DynamicValue { + instance; - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); - - static { - filter.add(SubType.ZOMBIE.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ZOMBIE); @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - int amount = game.getBattlefield().countAll(filter, sourceAbility.getControllerId(), game) + 2; - return amount; + return game.getBattlefield().countAll(filter, sourceAbility.getControllerId(), game) + 2; } @Override public LilianaZombiesCount copy() { - return new LilianaZombiesCount(); + return this; } @Override diff --git a/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java b/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java index 2c0ba23ba7c..ede372976fb 100644 --- a/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/MomirEmblem.java @@ -93,7 +93,7 @@ class MomirEffect extends OneShotEffect { if (expansionSet == null || !expansionSet.getSetType().isEternalLegal()) { options.remove(index); } else { - Card card = options.get(index).getCard(); + Card card = options.get(index).createCard(); if (card != null) { token = CopyTokenFunction.createTokenCopy(card, game); break; diff --git a/Mage/src/main/java/mage/game/draft/DraftCube.java b/Mage/src/main/java/mage/game/draft/DraftCube.java index 32af36e4898..d01888a5c39 100644 --- a/Mage/src/main/java/mage/game/draft/DraftCube.java +++ b/Mage/src/main/java/mage/game/draft/DraftCube.java @@ -15,7 +15,7 @@ import org.apache.log4j.Logger; */ public abstract class DraftCube { - public class CardIdentity { + public static class CardIdentity { private final String name; private final String extension; @@ -55,7 +55,7 @@ public abstract class DraftCube { protected List cubeCards = new ArrayList<>(); protected List leftCubeCards = new ArrayList<>(); - public DraftCube(String name) { + protected DraftCube(String name) { this.name = name; this.code = getClass().getSimpleName(); } @@ -96,7 +96,7 @@ public abstract class DraftCube { } if (cardInfo != null) { - booster.add(cardInfo.getCard()); + booster.add(cardInfo.createCard()); done = true; } else { logger.warn(new StringBuilder(this.getName()).append(" - Card not found: ").append(cardId.getName()).append(':').append(cardId.extension)); diff --git a/Mage/src/main/java/mage/game/draft/RemixedSet.java b/Mage/src/main/java/mage/game/draft/RemixedSet.java index 1d00693264c..ac64a38353f 100644 --- a/Mage/src/main/java/mage/game/draft/RemixedSet.java +++ b/Mage/src/main/java/mage/game/draft/RemixedSet.java @@ -81,7 +81,7 @@ public class RemixedSet implements Serializable { return; } CardInfo cardInfo = cards.remove(RandomUtil.nextInt(cards.size())); // so no duplicates in a booster - Card card = cardInfo.getCard(); + Card card = cardInfo.createCard(); if (card == null) { return; } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java index 584ce2ac794..5ecdee7e036 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java @@ -67,11 +67,9 @@ public abstract class DamagedBatchEvent extends GameEvent implements BatchGameEv public static DamagedBatchEvent makeEvent(DamagedEvent damagedEvent) { DamagedBatchEvent event; if (damagedEvent instanceof DamagedPlayerEvent) { - event = new DamagedBatchForPlayersEvent(); - event.addEvent(damagedEvent); + event = new DamagedBatchForPlayersEvent(damagedEvent); } else if (damagedEvent instanceof DamagedPermanentEvent) { - event = new DamagedBatchForPermanentsEvent(); - event.addEvent(damagedEvent); + event = new DamagedBatchForPermanentsEvent(damagedEvent); } else { throw new IllegalArgumentException("Wrong code usage. Unknown damage event for a new batch: " + damagedEvent.getClass().getName()); } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java new file mode 100644 index 00000000000..68ef57b5e06 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java @@ -0,0 +1,10 @@ +package mage.game.events; + +public class DamagedBatchForOnePermanentEvent extends DamagedBatchEvent { + + public DamagedBatchForOnePermanentEvent(DamagedEvent firstEvent) { + super(GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT, DamagedPermanentEvent.class); + addEvent(firstEvent); + setTargetId(firstEvent.getTargetId()); + } +} diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java index 01b632bddef..bedc02423a6 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java @@ -1,14 +1,13 @@ package mage.game.events; -import java.util.UUID; - /** * @author Susucr */ public class DamagedBatchForOnePlayerEvent extends DamagedBatchEvent { - public DamagedBatchForOnePlayerEvent(UUID playerId) { + public DamagedBatchForOnePlayerEvent(DamagedEvent firstEvent) { super(EventType.DAMAGED_BATCH_FOR_ONE_PLAYER, DamagedPlayerEvent.class); - this.setPlayerId(playerId); + setPlayerId(firstEvent.getPlayerId()); + addEvent(firstEvent); } } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java index 7af949f8d6f..aa5b2b9a6bc 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java @@ -5,7 +5,8 @@ package mage.game.events; */ public class DamagedBatchForPermanentsEvent extends DamagedBatchEvent { - public DamagedBatchForPermanentsEvent() { + public DamagedBatchForPermanentsEvent(DamagedEvent firstEvent) { super(EventType.DAMAGED_BATCH_FOR_PERMANENTS, DamagedPermanentEvent.class); + addEvent(firstEvent); } } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java index 54e0835d11e..d5fbaeea10e 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java @@ -5,7 +5,8 @@ package mage.game.events; */ public class DamagedBatchForPlayersEvent extends DamagedBatchEvent { - public DamagedBatchForPlayersEvent() { + public DamagedBatchForPlayersEvent(DamagedEvent firstEvent) { super(GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS, DamagedPlayerEvent.class); + addEvent(firstEvent); } } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 4041044f925..4a41084d94a 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -72,7 +72,8 @@ public class GameEvent implements Serializable { flag not used for this event */ ZONE_CHANGE, - ZONE_CHANGE_GROUP, + ZONE_CHANGE_GROUP, // between two specific zones only; TODO: rework all usages to ZONE_CHANGE_BATCH instead, see #11895 + ZONE_CHANGE_BATCH, // all zone changes that occurred from a single effect DRAW_CARDS, // event calls for multi draws only (if player draws 2+ cards at once) DRAW_CARD, DREW_CARD, EXPLORE, EXPLORED, // targetId is exploring permanent, playerId is its controller @@ -449,6 +450,11 @@ public class GameEvent implements Serializable { */ DAMAGED_BATCH_FOR_PERMANENTS, + /* DAMAGED_BATCH_FOR_ONE_PERMANENT + combines all permanent damage events to a single batch (event) and split it per damaged permanent + */ + DAMAGED_BATCH_FOR_ONE_PERMANENT, + DESTROY_PERMANENT, /* DESTROY_PERMANENT_BY_LEGENDARY_RULE targetId id of the permanent to destroy @@ -470,7 +476,6 @@ public class GameEvent implements Serializable { EVOLVED_CREATURE, EMBALMED_CREATURE, ETERNALIZED_CREATURE, - TRAINED_CREATURE, ATTACH, ATTACHED, UNATTACH, UNATTACHED, /* ATTACH, ATTACHED, @@ -585,6 +590,12 @@ public class GameEvent implements Serializable { playerId the player paying the cost */ EVIDENCE_COLLECTED, + /* Mentored Creature + targetId creature that was mentored + sourceId of the mentor ability + playerId controller of the creature mentoring + */ + MENTORED_CREATURE, //custom events CUSTOM_EVENT } @@ -807,4 +818,9 @@ public class GameEvent implements Serializable { protected void setSourceId(UUID sourceId) { this.sourceId = sourceId; } + + @Override + public String toString() { + return this.type.toString(); + } } diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java new file mode 100644 index 00000000000..da2b0bae48a --- /dev/null +++ b/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java @@ -0,0 +1,54 @@ +package mage.game.events; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class ZoneChangeBatchEvent extends GameEvent implements BatchGameEvent { + + private final Set events = new HashSet<>(); + + public ZoneChangeBatchEvent() { + super(EventType.ZONE_CHANGE_BATCH, null, null, null); + } + + @Override + public Set getEvents() { + return events; + } + + @Override + public Set getTargets() { + return events + .stream() + .map(GameEvent::getTargetId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Override + public int getAmount() { + return events + .stream() + .mapToInt(GameEvent::getAmount) + .sum(); + } + + @Override + @Deprecated // events can store a diff value, so search it from events list instead + public UUID getTargetId() { + throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); + } + + @Override + @Deprecated // events can store a diff value, so search it from events list instead + public UUID getSourceId() { + throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list."); + } + + public void addEvent(ZoneChangeEvent event) { + this.events.add(event); + } +} diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java index b6de6b9ab90..f0895c7d6c0 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java @@ -103,4 +103,8 @@ public class ZoneChangeEvent extends GameEvent { return this.source; } + @Override + public String toString() { + return super.toString() + ", from " + getFromZone() + " to " + getToZone(); + } } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 94146a544df..6ae7a036b65 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -4,7 +4,6 @@ import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.cards.Card; -import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Controllable; import mage.game.Game; @@ -113,8 +112,6 @@ public interface Permanent extends Card, Controllable { void setExpansionSetCode(String expansionSetCode); - void setRarity(Rarity rarity); - void setFlipCard(boolean flipCard); void setFlipCardName(String flipCardName); @@ -136,7 +133,7 @@ public interface Permanent extends Card, Controllable { boolean hasProtectionFrom(MageObject source, Game game); /** - * @param attachment + * @param attachment can be any object: card, permanent, token * @param source can be null for default checks like state base * @param game * @param silentMode - use it to ignore warning message for users (e.g. for @@ -194,7 +191,14 @@ public interface Permanent extends Card, Controllable { void reset(Game game); - MageObject getBasicMageObject(Game game); + /** + * Return original/blueprint/printable object (token or card) + *

    + * Original object used on each game cycle for permanent reset and apply all active effects + *

    + * Warning, all changes to the original object will be applied forever + */ + MageObject getBasicMageObject(); boolean destroy(Ability source, Game game); @@ -221,7 +225,7 @@ public interface Permanent extends Card, Controllable { * Add abilities to the permanent, can be used in effects * * @param ability - * @param sourceId + * @param sourceId can be null * @param game * @return can be null for exists abilities */ @@ -438,6 +442,10 @@ public interface Permanent extends Card, Controllable { boolean isMorphed(); + void setDisguised(boolean value); + + boolean isDisguised(); + void setManifested(boolean value); boolean isManifested(); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 9946f8cdd55..1d368d1ecb5 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -15,27 +15,34 @@ import mage.constants.SpellAbilityType; import mage.game.Game; import mage.game.events.ZoneChangeEvent; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; import java.util.UUID; /** * Static permanent on the battlefield. There are possible multiple permanents per one card, - * so be carefull for targets (ids are different) and ZCC (zcc is static for permanent). + * so be carefully for targets (ids are different) and ZCC (zcc is static for permanent). * * @author BetaSteward_at_googlemail.com */ -@SupportedSourceVersion(SourceVersion.RELEASE_8) public class PermanentCard extends PermanentImpl { - protected int maxLevelCounters; - // A copy of the origin card that was cast (this is not the original card, so it's possible to change some attribute to this blueprint to change attributes to the permanent if it enters the battlefield with e.g. a subtype) + // blueprint e.g. a copy of the original card that was cast + // (this is not the original card, so it's possible to change some attribute before it enters the battlefield) + // TODO: wtf, it modified on getCard/getBasicMageObject/getMainCard() and other places, e.g. on bestow -- must be fixed! protected Card card; - // the number this permanent instance had + + protected int maxLevelCounters; protected int zoneChangeCounter; public PermanentCard(Card card, UUID controllerId, Game game) { - super(card.getId(), card.getOwnerId(), controllerId, card.getName()); + super(card.getId(), card.getOwnerId(), controllerId, card.getName()); // card id + // TODO: wtf, must research - is it possible to have diff ids for same card id?! + // ETB with counters depends on card id, not permanent id + // TODO: ETB with counters works with tokens?! Must research + + // runtime check: must use real card only inside + if (card instanceof PermanentCard) { + throw new IllegalArgumentException("Wrong code usage: can't use PermanentCard inside another PermanentCard"); + } // usage check: you must put to play only real card's part // if you use it in test code then call CardUtil.getDefaultCardSideForBattlefield for default side @@ -49,6 +56,12 @@ public class PermanentCard extends PermanentImpl { goodForBattlefield = false; } } + + // face down cards allows in any forms (only face up restricted for non-permanents) + if (card.isFaceDown(game)) { + goodForBattlefield = true; + } + if (!goodForBattlefield) { throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName()); } @@ -101,6 +114,7 @@ public class PermanentCard extends PermanentImpl { } protected void copyFromCard(final Card card, final Game game) { + // TODO: must research - is it copy all fields or something miss this.name = card.getName(); this.abilities.clear(); if (this.faceDown) { @@ -118,7 +132,7 @@ public class PermanentCard extends PermanentImpl { this.abilities.setSourceId(objectId); this.cardType.clear(); this.cardType.addAll(card.getCardType()); - this.color = card.getColor(null).copy(); + this.color = card.getColor(game).copy(); this.frameColor = card.getFrameColor(game).copy(); this.frameStyle = card.getFrameStyle(); this.manaCost = card.getManaCost().copy(); @@ -128,10 +142,12 @@ public class PermanentCard extends PermanentImpl { this.subtype.copyFrom(card.getSubtype()); this.supertype.clear(); this.supertype.addAll(card.getSuperType()); + this.rarity = card.getRarity(); this.setExpansionSetCode(card.getExpansionSetCode()); this.setCardNumber(card.getCardNumber()); - this.rarity = card.getRarity(); + this.setImageFileName(card.getImageFileName()); + this.setImageNumber(card.getImageNumber()); this.usesVariousArt = card.getUsesVariousArt(); if (card.getSecondCardFace() != null) { @@ -146,7 +162,7 @@ public class PermanentCard extends PermanentImpl { } @Override - public MageObject getBasicMageObject(Game game) { + public MageObject getBasicMageObject() { return card; } @@ -166,10 +182,12 @@ public class PermanentCard extends PermanentImpl { @Override public boolean turnFaceUp(Ability source, Game game, UUID playerId) { if (super.turnFaceUp(source, game, playerId)) { + // TODO: miss types, abilities, color and other things for restore?! power.setModifiedBaseValue(power.getBaseValue()); toughness.setModifiedBaseValue(toughness.getBaseValue()); setManifested(false); setMorphed(false); + setDisguised(false); return true; } return false; @@ -208,12 +226,15 @@ public class PermanentCard extends PermanentImpl { @Override public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + // TODO: wtf, permanent must not change ZCC at all, is it buggy here?! card.updateZoneChangeCounter(game, event); zoneChangeCounter = card.getZoneChangeCounter(game); } @Override public void setZoneChangeCounter(int value, Game game) { + // TODO: wtf, why it sync card only without permanent zcc, is it buggy here?! + // TODO: miss zoneChangeCounter = card.getZoneChangeCounter(game); ? card.setZoneChangeCounter(value, game); } @@ -221,9 +242,4 @@ public class PermanentCard extends PermanentImpl { public Card getMainCard() { return card.getMainCard(); } - - @Override - public String toString() { - return card.toString(); - } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index d9f82ce9f01..5d77012f736 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -12,6 +12,7 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.RegenerateSourceEffect; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.hint.HintUtils; import mage.abilities.keyword.*; import mage.cards.Card; @@ -30,6 +31,7 @@ import mage.game.command.CommandObject; import mage.game.events.*; import mage.game.events.GameEvent.EventType; import mage.game.permanent.token.SquirrelToken; +import mage.game.permanent.token.Token; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; @@ -72,6 +74,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected boolean suspected; protected boolean manifested = false; protected boolean morphed = false; + protected boolean disguised = false; protected boolean ringBearerFlag = false; protected boolean canBeSacrificed = true; protected int classLevel = 1; @@ -118,6 +121,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected PermanentImpl(UUID ownerId, UUID controllerId, String name) { super(ownerId, name); + + // runtime check: need controller (if you catch it in non-game then use random uuid) + if (controllerId == null) { + throw new IllegalArgumentException("Wrong code usage: controllerId can't be null - " + name, new Throwable()); + } + this.originalControllerId = controllerId; this.controllerId = controllerId; this.counters = new Counters(); @@ -178,6 +187,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.protectorId = permanent.protectorId; this.morphed = permanent.morphed; + this.disguised = permanent.disguised; this.manifested = permanent.manifested; this.createOrder = permanent.createOrder; this.prototyped = permanent.prototyped; @@ -186,12 +196,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public String toString() { - StringBuilder sb = threadLocalBuilder.get(); - sb.append(this.getName()).append('-').append(this.getExpansionSetCode()); - if (copy) { - sb.append(" [Copy]"); - } - return sb.toString(); + String name = getName().isEmpty() + ? "face down" + " [" + getId().toString().substring(0, 3) + "]" + : getIdName(); + String imageInfo = getExpansionSetCode() + + ":" + getCardNumber() + + ":" + getImageFileName() + + ":" + getImageNumber(); + return name + + ", " + (getBasicMageObject() instanceof Token ? "T" : "C") + + ", " + getBasicMageObject().getClass().getSimpleName() + + ", " + imageInfo + + ", " + this.getPower() + "/" + this.getToughness() + + (this.isCopy() ? ", copy" : "") + + (this.isTapped() ? ", tapped" : ""); } @Override @@ -483,7 +501,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } @Override - protected UUID getControllerOrOwner() { + public UUID getControllerOrOwnerId() { return controllerId; } @@ -1222,18 +1240,24 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public boolean entersBattlefield(Ability source, Game game, Zone fromZone, boolean fireEvent) { controlledFromStartOfControllerTurn = false; - if (this.isFaceDown(game)) { // ?? add morphed/manifested here ??? + + BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.findFaceDownType(game, this); + if (faceDownType != null) { // remove some attributes here, because first apply effects comes later otherwise abilities (e.g. color related) will unintended trigger - MorphAbility.setPermanentToFaceDownCreature(this, this, game); + BecomesFaceDownCreatureEffect.makeFaceDownObject(game, null, this, faceDownType, null); } + // own etb event if (game.replaceEvent(new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone, EnterEventType.SELF))) { return false; } + + // normal etb event EntersTheBattlefieldEvent event = new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone); if (game.replaceEvent(event)) { return false; } + if (this.isPlaneswalker(game)) { int loyalty; if (this.getStartingLoyalty() == -2) { @@ -1886,6 +1910,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { morphed = value; } + @Override + public boolean isDisguised() { + return disguised; + } + + @Override + public void setDisguised(boolean value) { + disguised = value; + } + @Override public void setRarity(Rarity rarity) { this.rarity = rarity; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index 6145172324a..e51643b1d1a 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -22,10 +22,11 @@ public class PermanentToken extends PermanentImpl { // non-modifyable container with token characteristics // this PermanentToken resets to it on each game cycle + // TODO: see PermanentCard.card for usage research and fixes protected Token token; public PermanentToken(Token token, UUID controllerId, Game game) { - super(controllerId, controllerId, token.getName()); + super(controllerId, controllerId, token.getName()); // random id this.token = token.copy(); this.token.getAbilities().newOriginalId(); // neccessary if token has ability like DevourAbility() this.token.getAbilities().setSourceId(objectId); @@ -76,11 +77,6 @@ public class PermanentToken extends PermanentImpl { } } - @Override - public String toString() { - return String.format("%s - %s", getExpansionSetCode(), getName()); - } - private void copyFromToken(Token token, Game game, boolean reset) { // modify all attributes permanently (without game usage) this.name = token.getName(); @@ -119,7 +115,7 @@ public class PermanentToken extends PermanentImpl { } @Override - public MageObject getBasicMageObject(Game game) { + public MageObject getBasicMageObject() { return token; } diff --git a/Mage/src/main/java/mage/game/permanent/token/CragflameToken.java b/Mage/src/main/java/mage/game/permanent/token/CragflameToken.java new file mode 100644 index 00000000000..999a21b0109 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/CragflameToken.java @@ -0,0 +1,43 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.*; + +public final class CragflameToken extends TokenImpl { + + public CragflameToken() { + super("Cragflame", "Cragflame, a legendary colorless Equipment artifact token with \"Equipped creature gets +1/+1 and has vigilance, trample, and haste\" and equip {2}"); + supertype.add(SuperType.LEGENDARY); + cardType.add(CardType.ARTIFACT); + subtype.add(SubType.EQUIPMENT); + + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect( + VigilanceAbility.getInstance(), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield + ).setText("and has vigilance")); + ability.addEffect(new GainAbilityAttachedEffect( + TrampleAbility.getInstance(), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield + ).setText(", trample")); + ability.addEffect(new GainAbilityAttachedEffect( + HasteAbility.getInstance(), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield + ).setText(", and haste")); + this.addAbility(ability); + + this.addAbility(new EquipAbility(2)); + } + + private CragflameToken(final CragflameToken token) { + super(token); + } + + public CragflameToken copy() { + return new CragflameToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/EldraziSliverToken.java b/Mage/src/main/java/mage/game/permanent/token/EldraziSliverToken.java index f79d5a6b0ef..703b7aa974a 100644 --- a/Mage/src/main/java/mage/game/permanent/token/EldraziSliverToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/EldraziSliverToken.java @@ -12,7 +12,7 @@ import mage.constants.Zone; public final class EldraziSliverToken extends TokenImpl { public EldraziSliverToken() { - super("Eldrazi Sliver Token", "1/1 colorless Eldrazi Sliver creature token with \"Sacrifice this creature: Add {C}.\""); + super("Eldrazi Sliver Token", "1/1 colorless Eldrazi Sliver creature token. It has \"Sacrifice this creature: Add {C}.\""); cardType.add(CardType.CREATURE); subtype.add(SubType.ELDRAZI); subtype.add(SubType.SLIVER); diff --git a/Mage/src/main/java/mage/game/permanent/token/Gremlin11Token.java b/Mage/src/main/java/mage/game/permanent/token/Gremlin11Token.java new file mode 100644 index 00000000000..357e024db7e --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Gremlin11Token.java @@ -0,0 +1,28 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class Gremlin11Token extends TokenImpl { + + public Gremlin11Token() { + super("Gremlin Token", "1/1 red Gremlin creature token"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.GREMLIN); + color.setRed(true); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private Gremlin11Token(final Gremlin11Token token) { + super(token); + } + + public Gremlin11Token copy() { + return new Gremlin11Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/GutterGrimeToken.java b/Mage/src/main/java/mage/game/permanent/token/GutterGrimeToken.java index 9de070dc706..f286e8dd9d8 100644 --- a/Mage/src/main/java/mage/game/permanent/token/GutterGrimeToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/GutterGrimeToken.java @@ -1,5 +1,3 @@ - - package mage.game.permanent.token; import java.util.UUID; @@ -35,7 +33,7 @@ public final class GutterGrimeToken extends TokenImpl { color.setGreen(true); power = new MageInt(0); toughness = new MageInt(0); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetBasePowerToughnessSourceEffect(new GutterGrimeCounters(sourceId)))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetBasePowerToughnessSourceEffect(new GutterGrimeCountersCount(sourceId)))); } private GutterGrimeToken(final GutterGrimeToken token) { @@ -46,36 +44,37 @@ public final class GutterGrimeToken extends TokenImpl { return new GutterGrimeToken(this); } - class GutterGrimeCounters implements DynamicValue { +} - private final UUID sourceId; +class GutterGrimeCountersCount implements DynamicValue { - public GutterGrimeCounters(UUID sourceId) { - this.sourceId = sourceId; + private final UUID sourceId; + + public GutterGrimeCountersCount(UUID sourceId) { + this.sourceId = sourceId; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Permanent p = game.getPermanent(sourceId); + if (p != null) { + return p.getCounters(game).getCount(CounterType.SLIME); } + return 0; + } - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Permanent p = game.getPermanent(sourceId); - if (p != null) { - return p.getCounters(game).getCount(CounterType.SLIME); - } - return 0; - } + @Override + public GutterGrimeCountersCount copy() { + return this; + } - @Override - public GutterGrimeCounters copy() { - return this; - } + @Override + public String getMessage() { + return "slime counters on Gutter Grime"; + } - @Override - public String getMessage() { - return "slime counters on Gutter Grime"; - } - - @Override - public String toString() { - return "1"; - } + @Override + public String toString() { + return "1"; } } diff --git a/Mage/src/main/java/mage/game/permanent/token/ImpToken.java b/Mage/src/main/java/mage/game/permanent/token/ImpToken.java new file mode 100644 index 00000000000..8795b741890 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ImpToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; + +public final class ImpToken extends TokenImpl { + + public ImpToken() { + super("Imp Token", "2/2 red Imp creature token with \"When this creature dies, it deals 2 damage to each opponent.\""); + cardType.add(CardType.CREATURE); + subtype.add(SubType.IMP); + color.setRed(true); + power = new MageInt(2); + toughness = new MageInt(2); + + // When this creature dies, it deals 2 damage to each opponent. + this.addAbility(new DiesSourceTriggeredAbility( + new DamagePlayersEffect(2, TargetController.OPPONENT)).setTriggerPhrase("When this creature dies, ")); + } + + private ImpToken(final ImpToken token) { + super(token); + } + + public ImpToken copy() { + return new ImpToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java b/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java index 5837ce32ebb..a9a2820a55b 100644 --- a/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/IzoniSpiderToken.java @@ -21,7 +21,7 @@ public final class IzoniSpiderToken extends TokenImpl { toughness = new MageInt(1); // Menace - this.addAbility(new MenaceAbility()); + this.addAbility(new MenaceAbility(false)); // Reach this.addAbility(ReachAbility.getInstance()); diff --git a/Mage/src/main/java/mage/game/permanent/token/JunkToken.java b/Mage/src/main/java/mage/game/permanent/token/JunkToken.java new file mode 100644 index 00000000000..5fe029e0b65 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/JunkToken.java @@ -0,0 +1,36 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class JunkToken extends TokenImpl { + + public JunkToken() { + super("Junk Token", "Junk token"); + cardType.add(CardType.ARTIFACT); + subtype.add(SubType.JUNK); + + Ability ability = new ActivateAsSorceryActivatedAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), new TapSourceCost() + ); + ability.addCost(new SacrificeSourceCost().setText("sacrifice this artifact")); + this.addAbility(ability); + } + + private JunkToken(final JunkToken token) { + super(token); + } + + public JunkToken copy() { + return new JunkToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java b/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java index 209c22a0120..73ba1ce7adb 100644 --- a/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java @@ -11,7 +11,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.common.FilterCreatureCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInGraveyard; /** @@ -30,7 +30,7 @@ public final class NighteyesTheDesecratorToken extends TokenImpl { toughness = new MageInt(2); // {4}{B}: Put target creature card from a graveyard onto the battlefield under your control. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{4}{B}")); - ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage/src/main/java/mage/game/permanent/token/OgreToken.java b/Mage/src/main/java/mage/game/permanent/token/OgreToken.java index bd6298e7ba8..b088337122c 100644 --- a/Mage/src/main/java/mage/game/permanent/token/OgreToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/OgreToken.java @@ -10,7 +10,7 @@ import mage.constants.SubType; public final class OgreToken extends TokenImpl { public OgreToken() { - super("Ogre Token", "3/3 red Ogre creature"); + super("Ogre Token", "3/3 red Ogre creature token"); cardType.add(CardType.CREATURE); color.setRed(true); subtype.add(SubType.OGRE); diff --git a/Mage/src/main/java/mage/game/permanent/token/SalamnderWarriorToken.java b/Mage/src/main/java/mage/game/permanent/token/SalamanderWarriorToken.java similarity index 65% rename from Mage/src/main/java/mage/game/permanent/token/SalamnderWarriorToken.java rename to Mage/src/main/java/mage/game/permanent/token/SalamanderWarriorToken.java index 6484ae52191..967f58a3261 100644 --- a/Mage/src/main/java/mage/game/permanent/token/SalamnderWarriorToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/SalamanderWarriorToken.java @@ -7,9 +7,9 @@ import mage.constants.SubType; /** * @author TheElk801 */ -public final class SalamnderWarriorToken extends TokenImpl { +public final class SalamanderWarriorToken extends TokenImpl { - public SalamnderWarriorToken() { + public SalamanderWarriorToken() { super("Salamander Warrior Token", "4/3 blue Salamander Warrior creature token"); cardType.add(CardType.CREATURE); color.setBlue(true); @@ -19,11 +19,11 @@ public final class SalamnderWarriorToken extends TokenImpl { toughness = new MageInt(3); } - private SalamnderWarriorToken(final SalamnderWarriorToken token) { + private SalamanderWarriorToken(final SalamanderWarriorToken token) { super(token); } - public SalamnderWarriorToken copy() { - return new SalamnderWarriorToken(this); + public SalamanderWarriorToken copy() { + return new SalamanderWarriorToken(this); } } diff --git a/Mage/src/main/java/mage/game/permanent/token/SkeletonToken2.java b/Mage/src/main/java/mage/game/permanent/token/SkeletonToken2.java new file mode 100644 index 00000000000..14ed2800977 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SkeletonToken2.java @@ -0,0 +1,25 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +public class SkeletonToken2 extends TokenImpl { + + public SkeletonToken2() { + super("Skeleton Token", "2/1 black Skeleton creature token"); + cardType.add(CardType.CREATURE); + this.subtype.add(SubType.SKELETON); + color.setBlack(true); + power = new MageInt(2); + toughness = new MageInt(1); + } + + private SkeletonToken2(final SkeletonToken2 token) { + super(token); + } + + public SkeletonToken2 copy() { + return new SkeletonToken2(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java b/Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java new file mode 100644 index 00000000000..dd5d8a3a99a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +public class Thopter00ColorlessToken extends TokenImpl { + + public Thopter00ColorlessToken() { + super("Thopter Token", "0/0 colorless Thopter artifact creature token with flying"); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + subtype.add(SubType.THOPTER); + power = new MageInt(0); + toughness = new MageInt(0); + + addAbility(FlyingAbility.getInstance()); + } + + private Thopter00ColorlessToken(final Thopter00ColorlessToken token) { + super(token); + } + + @Override + public Thopter00ColorlessToken copy() { + return new Thopter00ColorlessToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/TinyToken.java b/Mage/src/main/java/mage/game/permanent/token/TinyToken.java new file mode 100644 index 00000000000..64280f4b720 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/TinyToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.TrampleAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +/** + * @author PurpleCrowbar + */ +public final class TinyToken extends TokenImpl { + + public TinyToken() { + super("Tiny", "Tiny, a legendary 2/2 green Dog Detective creature token with trample"); + supertype.add(SuperType.LEGENDARY); + cardType.add(CardType.CREATURE); + color.setGreen(true); + subtype.add(SubType.DOG, SubType.DETECTIVE); + power = new MageInt(2); + toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + } + + private TinyToken(final TinyToken token) { + super(token); + } + + public TinyToken copy() { + return new TinyToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java index ce7cfaecf39..f8bf3cacce0 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java +++ b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java @@ -21,6 +21,7 @@ import mage.game.permanent.PermanentToken; import mage.game.permanent.token.custom.CreatureToken; import mage.players.Player; import mage.target.Target; +import org.apache.log4j.Logger; import java.lang.reflect.Constructor; import java.util.*; @@ -30,6 +31,8 @@ import java.util.*; */ public abstract class TokenImpl extends MageObjectImpl implements Token { + private static final Logger logger = Logger.getLogger(MageObjectImpl.class); + protected String description; private final ArrayList lastAddedTokenIds = new ArrayList<>(); @@ -142,6 +145,14 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, null); } + /** + * Find random token image from a database + * + * @param token + * @param game + * @param sourceId + * @return + */ public static TokenInfo generateTokenInfo(TokenImpl token, Game game, UUID sourceId) { // Choose a token image by priority: // - use source's set code @@ -190,11 +201,10 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { // TODO: return default creature token image } - // TODO: implement Copy image - // TODO: implement Manifest image - // TODO: implement Morph image - - // unknown tokens + // unknown tokens: + // - without official token sets; + // - un-implemented token set (must add missing images to tokens database); + // - another use cases with unknown tokens return new TokenInfo(TokenType.TOKEN, "Unknown", TokenRepository.XMAGE_TOKENS_SET_CODE, 0); } diff --git a/Mage/src/main/java/mage/game/permanent/token/TolsimirMidnightsLightToken.java b/Mage/src/main/java/mage/game/permanent/token/TolsimirMidnightsLightToken.java new file mode 100644 index 00000000000..fd06447e4d4 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/TolsimirMidnightsLightToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.TrampleAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +public class TolsimirMidnightsLightToken extends TokenImpl { + + public TolsimirMidnightsLightToken() { + super("Voja Fenstalker", "Voja Fenstalker, a legendary 5/5 green and white Wolf creature token with trample"); + this.supertype.add(SuperType.LEGENDARY); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.color.setGreen(true); + this.color.setWhite(true); + this.subtype.add(SubType.WOLF); + this.cardType.add(CardType.CREATURE); + + addAbility(TrampleAbility.getInstance()); + } + + private TolsimirMidnightsLightToken(final TolsimirMidnightsLightToken token) { + super(token); + } + + @Override + public TolsimirMidnightsLightToken copy() { + return new TolsimirMidnightsLightToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/custom/XmageToken.java b/Mage/src/main/java/mage/game/permanent/token/custom/XmageToken.java new file mode 100644 index 00000000000..3c3e11149c4 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/custom/XmageToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token.custom; + +import mage.abilities.Ability; +import mage.cards.FrameStyle; +import mage.game.permanent.token.TokenImpl; + +/** + * GUI: inner xmage token object to show custom images (information tokens like morph, blessing, etc) + * + * @author JayDi85 + */ +public final class XmageToken extends TokenImpl { + + public XmageToken(String name) { + super(name, ""); + this.frameStyle = FrameStyle.BFZ_FULL_ART_BASIC; // use full art for better visual in card viewer + } + + public XmageToken withAbility(Ability ability) { + this.addAbility(ability); + return this; + } + + private XmageToken(final XmageToken token) { + super(token); + } + + public XmageToken copy() { + return new XmageToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index c71da794378..a6a723bca8c 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -6,7 +6,6 @@ import mage.abilities.costs.mana.ActivationManaAbilityStep; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.keyword.BestowAbility; -import mage.abilities.keyword.MorphAbility; import mage.abilities.keyword.PrototypeAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.*; @@ -83,13 +82,13 @@ public class Spell extends StackObjectImpl implements Card { // simulate another side as new card (another code part in continues effect from disturb ability) affectedCard = TransformAbility.transformCardSpellStatic(card, card.getSecondCardFace(), game); } - if (ability instanceof PrototypeAbility){ - affectedCard = ((PrototypeAbility)ability).prototypeCardSpell(card); + if (ability instanceof PrototypeAbility) { + affectedCard = ((PrototypeAbility) ability).prototypeCardSpell(card); this.prototyped = true; } this.card = affectedCard; - this.manaCost = this.card.getManaCost().copy(); + this.manaCost = affectedCard.getManaCost().copy(); this.color = affectedCard.getColor(null).copy(); this.frameColor = affectedCard.getFrameColor(null).copy(); this.frameStyle = affectedCard.getFrameStyle(); @@ -100,7 +99,10 @@ public class Spell extends StackObjectImpl implements Card { this.ability = ability; this.ability.setControllerId(controllerId); - if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.MORPH){ + if (ability.getSpellAbilityCastMode().isFaceDown()) { + // TODO: need research: + // - why it use game param for color and subtype (possible bug?) + // - is it possible to use BecomesFaceDownCreatureEffect.makeFaceDownObject or like that? this.faceDown = true; this.getColor(game).setColor(null); game.getState().getCreateMageObjectAttribute(this.getCard(), game).getSubtype().clear(); @@ -199,8 +201,10 @@ public class Spell extends StackObjectImpl implements Card { } public String getSpellCastText(Game game) { - if (this.getSpellAbility() instanceof MorphAbility) { - return "a card face down"; + if (this.getSpellAbility().getSpellAbilityCastMode().isFaceDown()) { + // add face down name with object link, so user can look at it from logs + return "a " + GameLog.getColoredObjectIdName(this.getSpellAbility().getCharacteristics(game)) + + " using " + this.getSpellAbility().getSpellAbilityCastMode(); } if (card instanceof AdventureCardSpell) { @@ -238,6 +242,16 @@ public class Spell extends StackObjectImpl implements Card { throw new IllegalStateException("Wrong code usage: you can't change card number for the spell"); } + @Override + public String getImageFileName() { + return card.getImageFileName(); + } + + @Override + public void setImageFileName(String imageFile) { + throw new IllegalStateException("Wrong code usage: you can't change image file name for the spell"); + } + @Override public Integer getImageNumber() { return card.getImageNumber(); @@ -509,6 +523,11 @@ public class Spell extends StackObjectImpl implements Card { return this.controllerId; } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override public String getName() { return card.getName(); @@ -546,6 +565,11 @@ public class Spell extends StackObjectImpl implements Card { return card.getRarity(); } + @Override + public void setRarity(Rarity rarity) { + throw new IllegalArgumentException("Un-supported operation: " + this, new Throwable()); + } + @Override public List getCardType(Game game) { if (faceDown) { @@ -654,7 +678,9 @@ public class Spell extends StackObjectImpl implements Card { } @Override - public void setManaCost(ManaCosts costs) { this.manaCost = costs.copy(); } + public void setManaCost(ManaCosts costs) { + this.manaCost = costs.copy(); + } /** * 202.3b When calculating the converted mana cost of an object with an {X} @@ -765,14 +791,12 @@ public class Spell extends StackObjectImpl implements Card { @Override public boolean turnFaceUp(Ability source, Game game, UUID playerId) { - setFaceDown(false, game); - return true; + throw new IllegalStateException("Spells un-support turn face up commands"); } @Override public boolean turnFaceDown(Ability source, Game game, UUID playerId) { - setFaceDown(true, game); - return true; + throw new IllegalStateException("Spells un-support turn face up commands"); } @Override @@ -933,6 +957,11 @@ public class Spell extends StackObjectImpl implements Card { return card.getUsesVariousArt(); } + @Override + public void setUsesVariousArt(boolean usesVariousArt) { + card.setUsesVariousArt(usesVariousArt); + } + @Override public List getMana() { return card.getMana(); @@ -1104,8 +1133,8 @@ public class Spell extends StackObjectImpl implements Card { } @Override - public void checkForCountersToAdd(Permanent permanent, Ability source, Game game) { - card.checkForCountersToAdd(permanent, source, game); + public void applyEnterWithCounters(Permanent permanent, Ability source, Game game) { + card.applyEnterWithCounters(permanent, source, game); } @Override diff --git a/Mage/src/main/java/mage/game/stack/SpellStack.java b/Mage/src/main/java/mage/game/stack/SpellStack.java index 14c4c0efb1c..389495d22e4 100644 --- a/Mage/src/main/java/mage/game/stack/SpellStack.java +++ b/Mage/src/main/java/mage/game/stack/SpellStack.java @@ -143,6 +143,6 @@ public class SpellStack extends ArrayDeque { @Override public String toString() { - return this.size() + (this.isEmpty() ? "" : " (top: " + CardUtil.substring(this.getFirst().toString(), 100) + ")"); + return this.size() + (this.isEmpty() ? "" : " (top: " + CardUtil.substring(this.getFirst().toString(), 100, "...") + ")"); } } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index a491cea94b5..722e44b704d 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -147,6 +147,16 @@ public class StackAbility extends StackObjectImpl implements Ability { throw new IllegalStateException("Wrong code usage: you can't change card number for the stack ability"); } + @Override + public String getImageFileName() { + return ""; + } + + @Override + public void setImageFileName(String imageFile) { + throw new IllegalStateException("Wrong code usage: you can't change image file name for the stack ability"); + } + @Override public Integer getImageNumber() { return 0; @@ -301,6 +311,11 @@ public class StackAbility extends StackObjectImpl implements Ability { return this.controllerId; } + @Override + public UUID getControllerOrOwnerId() { + return getControllerId(); + } + @Override public Costs getCosts() { return emptyCosts; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 43c8db88362..5fedee1b40b 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -294,10 +294,8 @@ public interface Player extends MageItem, Copyable { void setTopCardRevealed(boolean topCardRevealed); /** - * Get data from the client Preferences (e.g. avatarId or - * showAbilityPickerForce) - * - * @return + * User's settings like avatar or skip buttons. + * WARNING, game related code must use controlling player settings only, e.g. getControllingPlayersUserData */ UserData getUserData(); @@ -333,6 +331,9 @@ public interface Player extends MageItem, Copyable { List getTurnControllers(); + /** + * Current turn controller for a player (return own id for own control) + */ UUID getTurnControlledBy(); /** @@ -360,6 +361,11 @@ public interface Player extends MageItem, Copyable { */ void setGameUnderYourControl(boolean value); + /** + * Return player's turn control to prev player + * @param value + * @param fullRestore return turn control to own + */ void setGameUnderYourControl(boolean value, boolean fullRestore); void setTestMode(boolean value); @@ -982,7 +988,7 @@ public interface Player extends MageItem, Copyable { * @param source * @param game * @param fromZone - * @param withName + * @param withName for face down: used to hide card name in game logs before real face down status apply * @return */ @Deprecated diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d0d917f5a8e..1fc300ac559 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -159,10 +159,12 @@ public abstract class PlayerImpl implements Player, Serializable { protected FilterPermanent sacrificeCostFilter; protected final List alternativeSourceCosts = new ArrayList<>(); - protected boolean isGameUnderControl = true; - protected UUID turnController; - protected List turnControllers = new ArrayList<>(); - protected Set playersUnderYourControl = new HashSet<>(); + // TODO: rework turn controller to use single list (see other todos) + //protected Stack allTurnControllers = new Stack<>(); + protected boolean isGameUnderControl = true; // TODO: replace with allTurnControllers.isEmpty + protected UUID turnController; // null on own control TODO: replace with allTurnControllers.last + protected List turnControllers = new ArrayList<>(); // TODO: remove + protected Set playersUnderYourControl = new HashSet<>(); // TODO: replace with game method and search in allTurnControllers protected Set usersAllowedToSeeHandCards = new HashSet<>(); protected List attachments = new ArrayList<>(); @@ -263,14 +265,14 @@ public abstract class PlayerImpl implements Player, Serializable { this.storedBookmark = player.storedBookmark; this.topCardRevealed = player.topCardRevealed; - this.playersUnderYourControl.addAll(player.playersUnderYourControl); this.usersAllowedToSeeHandCards.addAll(player.usersAllowedToSeeHandCards); this.isTestMode = player.isTestMode; - this.isGameUnderControl = player.isGameUnderControl; + this.isGameUnderControl = player.isGameUnderControl; this.turnController = player.turnController; this.turnControllers.addAll(player.turnControllers); + this.playersUnderYourControl.addAll(player.playersUnderYourControl); this.passed = player.passed; this.passedTurn = player.passedTurn; @@ -363,13 +365,14 @@ public abstract class PlayerImpl implements Player, Serializable { this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts()); this.topCardRevealed = player.isTopCardRevealed(); - this.playersUnderYourControl.clear(); - this.playersUnderYourControl.addAll(player.getPlayersUnderYourControl()); - this.isGameUnderControl = player.isGameUnderControl(); - this.turnController = player.getTurnControlledBy(); + this.isGameUnderControl = player.isGameUnderControl(); + this.turnController = this.getId().equals(player.getTurnControlledBy()) ? null : player.getTurnControlledBy(); this.turnControllers.clear(); this.turnControllers.addAll(player.getTurnControllers()); + this.playersUnderYourControl.clear(); + this.playersUnderYourControl.addAll(player.getPlayersUnderYourControl()); + this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving(); this.clearCastSourceIdManaCosts(); @@ -607,6 +610,9 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void setTurnControlledBy(UUID playerId) { + if (playerId == null) { + throw new IllegalArgumentException("Can't add unknown player to turn controllers: " + playerId); + } this.turnController = playerId; this.turnControllers.add(playerId); } @@ -618,7 +624,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public UUID getTurnControlledBy() { - return this.turnController; + return this.turnController == null ? this.getId() : this.turnController; } @Override @@ -647,14 +653,18 @@ public abstract class PlayerImpl implements Player, Serializable { this.isGameUnderControl = value; if (isGameUnderControl) { if (fullRestore) { + // to own this.turnControllers.clear(); - this.turnController = getId(); + this.turnController = null; + this.isGameUnderControl = true; } else { + // to prev player if (!turnControllers.isEmpty()) { this.turnControllers.remove(turnControllers.size() - 1); } if (turnControllers.isEmpty()) { - this.turnController = getId(); + this.turnController = null; + this.isGameUnderControl = true; } else { this.turnController = turnControllers.get(turnControllers.size() - 1); isGameUnderControl = false; @@ -832,13 +842,13 @@ public abstract class PlayerImpl implements Player, Serializable { private boolean doDiscard(Card card, Ability source, Game game, boolean payForCost, boolean fireFinalEvent) { //20100716 - 701.7 /* 701.7. Discard # - 701.7a To discard a card, move it from its owner’s hand to that player’s graveyard. + 701.7a To discard a card, move it from its owner's hand to that player's graveyard. 701.7b By default, effects that cause a player to discard a card allow the affected player to choose which card to discard. Some effects, however, require a random discard or allow another player to choose which card is discarded. 701.7c If a card is discarded, but an effect causes it to be put into a hidden zone - instead of into its owner’s graveyard without being revealed, all values of that - card’s characteristics are considered to be undefined. + instead of into its owner's graveyard without being revealed, all values of that + card's characteristics are considered to be undefined. TODO: If a card is discarded this way to pay a cost that specifies a characteristic about the discarded card, that cost payment is illegal; the game returns to @@ -975,8 +985,11 @@ public abstract class PlayerImpl implements Player, Serializable { } } else { // user defined order + UUID cardOwner = cards.getRandom(game).getOwnerId(); TargetCard target = new TargetCard(Zone.ALL, - new FilterCard("card ORDER to put on the BOTTOM of your library (last one chosen will be bottommost)")); + new FilterCard("card ORDER to put on the BOTTOM of " + + (cardOwner.equals(playerId) ? "your" : game.getPlayer(cardOwner).getName() + "'s") + + " library (last one chosen will be bottommost)")); target.setRequired(true); while (cards.size() > 1 && this.canRespond() && this.choose(Outcome.Neutral, cards, target, source, game)) { @@ -1068,8 +1081,11 @@ public abstract class PlayerImpl implements Player, Serializable { } } else { // user defined order + UUID cardOwner = cards.getRandom(game).getOwnerId(); TargetCard target = new TargetCard(Zone.ALL, - new FilterCard("card ORDER to put on the TOP of your library (last one chosen will be topmost)")); + new FilterCard("card ORDER to put on the TOP of " + + (cardOwner.equals(playerId) ? "your" : game.getPlayer(cardOwner).getName() + "'s") + + " library (last one chosen will be topmost)")); target.setRequired(true); while (cards.size() > 1 && this.canRespond() @@ -1366,7 +1382,7 @@ public abstract class PlayerImpl implements Player, Serializable { NOT_REQUIRED_NO_CHOICE, } - private class ApprovingObjectResult { + private static class ApprovingObjectResult { public final ApprovingObjectResultStatus status; public final ApprovingObject approvingObject; // not null iff status is CHOSEN @@ -1839,7 +1855,7 @@ public abstract class PlayerImpl implements Player, Serializable { int last = cards.size(); for (Card card : cards.getCards(game)) { current++; - sb.append(GameLog.getColoredObjectName(card)); + sb.append(GameLog.getColoredObjectName(card)); // TODO: see same usage in OfferingAbility for hide card's id (is it needs for reveal too?!) if (current < last) { sb.append(", "); } @@ -2515,7 +2531,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void concede(Game game) { game.setConcedingPlayer(playerId); - lost(game); + + lost(game); // it's ok to be ignored by "can't lose abilities" here (setConcedingPlayer done all work above) } @Override @@ -4688,7 +4705,7 @@ public abstract class PlayerImpl implements Player, Serializable { Player eventPlayer = game.getPlayer(info.event.getPlayerId()); if (eventPlayer != null && fromZone != null) { game.informPlayers(eventPlayer.getLogName() + " puts " - + (info.faceDown ? "a card face down " : permanent.getLogName()) + " from " + + GameLog.getColoredObjectIdName(permanent) + " from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " onto the Battlefield" + CardUtil.getSourceLogName(game, source, permanent.getId())); } @@ -4714,10 +4731,23 @@ public abstract class PlayerImpl implements Player, Serializable { break; case EXILED: for (Card card : cards) { + // 708.9. + // If a face-down permanent or a face-down component of a merged permanent moves from the + // battlefield to any other zone, its owner must reveal it to all players as they move it. + // If a face-down spell moves from the stack to any zone other than the battlefield, + // its owner must reveal it to all players as they move it. If a player leaves the game, + // all face-down permanents, face-down components of merged permanents, and face-down spells + // owned by that player must be revealed to all players. At the end of each game, all + // face-down permanents, face-down components of merged permanents, and face-down spells must + // be revealed to all players. + + // force to show face down name in logs + // TODO: replace it to real reveal code somehow? fromZone = game.getState().getZone(card.getId()); boolean withName = (fromZone == Zone.BATTLEFIELD || fromZone == Zone.STACK) || !card.isFaceDown(game); + if (moveCardToExileWithInfo(card, null, "", source, game, fromZone, withName)) { successfulMovedCards.add(card); } @@ -4995,10 +5025,19 @@ public abstract class PlayerImpl implements Player, Serializable { } } if (Zone.EXILED.equals(game.getState().getZone(card.getId()))) { // only if target zone was not replaced - game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() - + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' - + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); + String visibleName; + if (withName) { + // warning, withName param used to forced name show of the face down card (see 708.9.) + if (card.getName().isEmpty()) { + throw new IllegalStateException("Wrong code usage: method must find real card name, but found nothing", new Throwable()); + } + visibleName = card.getLogName() + (card.isCopy() ? " (Copy)" : ""); + } else { + visibleName = "a " + GameLog.getNeutralObjectIdName(EmptyNames.FACE_DOWN_CARD.toString(), card.getId()); + } + game.informPlayers(this.getLogName() + " moves " + visibleName + + (fromZone != null ? " from " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "") + + " to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); } } diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index 882a0314d40..8e674c693b9 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -139,6 +139,10 @@ public interface Target extends Serializable { boolean isRandom(); + /** + * WARNING, if you need random choice then call it by target's choose method, not player's choose + * see https://github.com/magefree/mage/issues/11933 + */ void setRandom(boolean atRandom); UUID getFirstTarget(); diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index f6adbeb4e0b..a03cc1052cd 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -34,7 +34,7 @@ public abstract class TargetImpl implements Target { protected boolean chosen = false; // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; - protected boolean atRandom = false; + protected boolean atRandom = false; // for inner choose logic protected UUID targetController = null; // if null the ability controller is the targetController protected UUID abilityController = null; // only used if target controller != ability controller diff --git a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayer.java b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayer.java index f977286b797..874ee9aa7cb 100644 --- a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayer.java +++ b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayer.java @@ -26,18 +26,10 @@ public class TargetCreatureOrPlayer extends TargetImpl { this(1, 1, new FilterCreatureOrPlayer()); } - public TargetCreatureOrPlayer(int numTargets) { - this(numTargets, numTargets, new FilterCreatureOrPlayer()); - } - public TargetCreatureOrPlayer(FilterCreatureOrPlayer filter) { this(1, 1, filter); } - public TargetCreatureOrPlayer(int numTargets, int maxNumTargets) { - this(numTargets, maxNumTargets, new FilterCreatureOrPlayer()); - } - public TargetCreatureOrPlayer(int minNumTargets, int maxNumTargets, FilterCreatureOrPlayer filter) { this.minNumberOfTargets = minNumTargets; this.maxNumberOfTargets = maxNumTargets; diff --git a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java index 69863e89027..a9e90993042 100644 --- a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java @@ -15,11 +15,7 @@ import java.util.stream.Collectors; public class EachTargetPointer extends TargetPointerImpl { - private Map zoneChangeCounter = new HashMap<>(); - - public static EachTargetPointer getInstance() { - return new EachTargetPointer(); - } + private final Map zoneChangeCounter = new HashMap<>(); public EachTargetPointer() { super(); @@ -27,15 +23,16 @@ public class EachTargetPointer extends TargetPointerImpl { protected EachTargetPointer(final EachTargetPointer targetPointer) { super(targetPointer); - - this.zoneChangeCounter = new HashMap<>(); - for (Map.Entry entry : targetPointer.zoneChangeCounter.entrySet()) { - this.zoneChangeCounter.put(entry.getKey(), entry.getValue()); - } + this.zoneChangeCounter.putAll(targetPointer.zoneChangeCounter); } @Override public void init(Game game, Ability source) { + if (isInitialized()) { + return; + } + this.setInitialized(); + if (!source.getTargets().isEmpty()) { for (UUID target : source .getTargets() @@ -99,17 +96,6 @@ public class EachTargetPointer extends TargetPointerImpl { return new EachTargetPointer(this); } - @Override - public FixedTarget getFixedTarget(Game game, Ability source) { - this.init(game, source); - UUID firstId = getFirst(game, source); - if (firstId != null) { - return new FixedTarget(firstId, game.getState().getZoneChangeCounter(firstId)); - } - return null; - - } - @Override public Permanent getFirstTargetPermanentOrLKI(Game game, Ability source) { UUID targetId = source.getFirstTarget(); diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java b/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java index 70416666fed..a9004ba9aa2 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java @@ -16,10 +16,11 @@ import java.util.UUID; public class FixedTarget extends TargetPointerImpl { private final UUID targetId; - private int zoneChangeCounter; - private boolean initialized; + private int zoneChangeCounter = 0; /** + * Dynamic ZCC (not recommended) + *

    * Use this best only to target to a player or spells on the stack. Try to * avoid this method to set the target to a specific card or permanent if * possible. Because the zoneChangeCounter is not set immediately, it can be @@ -32,7 +33,6 @@ public class FixedTarget extends TargetPointerImpl { public FixedTarget(UUID target) { super(); this.targetId = target; - this.initialized = false; } public FixedTarget(MageObjectReference mor) { @@ -40,7 +40,9 @@ public class FixedTarget extends TargetPointerImpl { } /** - * Target counter is immediatly initialised with current zoneChangeCounter + * Static ZCC + *

    + * Target counter is immediately initialised with current zoneChangeCounter * value from the GameState Sets fixed the currect zoneChangeCounter * * @param card used to get the objectId @@ -50,10 +52,13 @@ public class FixedTarget extends TargetPointerImpl { super(); this.targetId = card.getId(); this.zoneChangeCounter = card.getZoneChangeCounter(game); - this.initialized = true; + + this.setInitialized(); // no need dynamic init } /** + * Static ZCC + *

    * Target counter is immediately initialized with current zoneChangeCounter * value from the given permanent * @@ -65,6 +70,8 @@ public class FixedTarget extends TargetPointerImpl { } /** + * Static ZCC + *

    * Use this if you already want to fix the target object to the known zone * now (otherwise the zone will be set if the ability triggers or not at * all) If not initialized, the object of the current zone then will be @@ -76,11 +83,14 @@ public class FixedTarget extends TargetPointerImpl { public FixedTarget(UUID targetId, int zoneChangeCounter) { super(); this.targetId = targetId; - this.initialized = true; this.zoneChangeCounter = zoneChangeCounter; + + this.setInitialized(); // no need dynamic init } /** + * Static ZCC + *

    * Use this to set the target to exactly the zone the target is currently in * * @param targetId @@ -89,8 +99,9 @@ public class FixedTarget extends TargetPointerImpl { public FixedTarget(UUID targetId, Game game) { super(); this.targetId = targetId; - this.initialized = true; this.zoneChangeCounter = game.getState().getZoneChangeCounter(targetId); + + this.setInitialized(); // no need dynamic init } protected FixedTarget(final FixedTarget targetPointer) { @@ -98,15 +109,16 @@ public class FixedTarget extends TargetPointerImpl { this.targetId = targetPointer.targetId; this.zoneChangeCounter = targetPointer.zoneChangeCounter; - this.initialized = targetPointer.initialized; } @Override public void init(Game game, Ability source) { - if (!initialized) { - initialized = true; - this.zoneChangeCounter = game.getState().getZoneChangeCounter(targetId); + if (isInitialized()) { + return; } + setInitialized(); + + this.zoneChangeCounter = game.getState().getZoneChangeCounter(targetId); } /** @@ -161,12 +173,6 @@ public class FixedTarget extends TargetPointerImpl { return zoneChangeCounter; } - @Override - public FixedTarget getFixedTarget(Game game, Ability source) { - init(game, source); - return this; - } - @Override public Permanent getFirstTargetPermanentOrLKI(Game game, Ability source) { init(game, source); diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java index 78d26932f87..af3d80dbb31 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java @@ -14,80 +14,61 @@ import java.util.*; import java.util.stream.Collectors; /** + * Targets list with static ZCC + * * @author LevelX2 */ public class FixedTargets extends TargetPointerImpl { final ArrayList targets = new ArrayList<>(); - final ArrayList targetsNotInitialized = new ArrayList<>(); - private boolean initialized; - - public FixedTargets(UUID targetId) { - super(); - - targetsNotInitialized.add(targetId); - this.initialized = false; + public FixedTargets(List objects, Game game) { + this(objects + .stream() + .map(o -> new MageObjectReference(o.getId(), game)) + .collect(Collectors.toList())); } - public FixedTargets(Cards cards, Game game) { - super(); - if (cards != null) { - for (UUID targetId : cards) { - MageObjectReference mor = new MageObjectReference(targetId, game); - targets.add(mor); - } - } - this.initialized = true; + public FixedTargets(Set objects, Game game) { + this(objects + .stream() + .map(o -> new MageObjectReference(o.getId(), game)) + .collect(Collectors.toList())); + } + + public FixedTargets(Cards objects, Game game) { + this(objects.getCards(game) + .stream() + .map(o -> new MageObjectReference(o.getId(), game)) + .collect(Collectors.toList())); } public FixedTargets(Token token, Game game) { - this(token.getLastAddedTokenIds().stream().map(game::getPermanent).collect(Collectors.toList()), game); + this(token.getLastAddedTokenIds() + .stream() + .map(game::getPermanent) + .collect(Collectors.toList()), game); } - public FixedTargets(List permanents, Game game) { + public FixedTargets(List morList) { super(); - - for (Permanent permanent : permanents) { - MageObjectReference mor = new MageObjectReference(permanent.getId(), permanent.getZoneChangeCounter(game), game); - targets.add(mor); - } - this.initialized = true; + targets.addAll(morList); + this.setInitialized(); // no need dynamic init } - public FixedTargets(Set cards, Game game) { - super(); - - for (Card card : cards) { - MageObjectReference mor = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game), game); - targets.add(mor); - } - this.initialized = true; - } - - public FixedTargets(Collection morSet, Game game) { - super(); - - targets.addAll(morSet); - this.initialized = true; - } - - private FixedTargets(final FixedTargets targetPointer) { - super(targetPointer); - - this.targets.addAll(targetPointer.targets); - this.targetsNotInitialized.addAll(targetPointer.targetsNotInitialized); - this.initialized = targetPointer.initialized; + private FixedTargets(final FixedTargets pointer) { + super(pointer); + this.targets.addAll(pointer.targets); } @Override public void init(Game game, Ability source) { - if (!initialized) { - initialized = true; - for (UUID targetId : targetsNotInitialized) { - targets.add(new MageObjectReference(targetId, game.getState().getZoneChangeCounter(targetId), game)); - } + if (isInitialized()) { + return; } + + // impossible use case + throw new IllegalArgumentException("Wrong code usage: FixedTargets support only static ZCC, you can't get here"); } @Override @@ -118,23 +99,6 @@ public class FixedTargets extends TargetPointerImpl { return new FixedTargets(this); } - /** - * Returns a fixed target for (and only) the first taget - * - * @param game - * @param source - * @return - */ - @Override - public FixedTarget getFixedTarget(Game game, Ability source) { - this.init(game, source); - UUID firstId = getFirst(game, source); - if (firstId != null) { - return new FixedTarget(firstId, game.getState().getZoneChangeCounter(firstId)); - } - return null; - } - @Override public Permanent getFirstTargetPermanentOrLKI(Game game, Ability source) { UUID targetId = null; @@ -143,8 +107,6 @@ public class FixedTargets extends TargetPointerImpl { MageObjectReference mor = targets.get(0); targetId = mor.getSourceId(); zoneChangeCounter = mor.getZoneChangeCounter(); - } else if (!targetsNotInitialized.isEmpty()) { - targetId = targetsNotInitialized.get(0); } if (targetId != null) { Permanent permanent = game.getPermanent(targetId); diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java index a4f2726e404..88d2ea32320 100644 --- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java @@ -1,6 +1,6 @@ package mage.target.targetpointer; -import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.cards.Card; import mage.constants.Zone; @@ -15,154 +15,148 @@ import java.util.*; */ public abstract class NthTargetPointer extends TargetPointerImpl { - private static final Map emptyZoneChangeCounter = Collections.unmodifiableMap(new HashMap<>(0)); private static final List emptyTargets = Collections.unmodifiableList(new ArrayList<>(0)); - private Map zoneChangeCounter; - private final int targetNumber; + // TODO: rework to list of MageObjectReference instead zcc + private final Map zoneChangeCounter = new HashMap<>(); + private final int targetIndex; // zero-based target numbers (1 -> 0, 2 -> 1, 3 -> 2, etc) public NthTargetPointer(int targetNumber) { super(); - this.targetNumber = targetNumber; + this.targetIndex = targetNumber - 1; } protected NthTargetPointer(final NthTargetPointer nthTargetPointer) { super(nthTargetPointer); - this.targetNumber = nthTargetPointer.targetNumber; - - if (nthTargetPointer.zoneChangeCounter != null) { - this.zoneChangeCounter = new HashMap<>(nthTargetPointer.zoneChangeCounter.size()); - for (Map.Entry entry : nthTargetPointer.zoneChangeCounter.entrySet()) { - addToZoneChangeCounter(entry.getKey(), entry.getValue()); - } - } + this.targetIndex = nthTargetPointer.targetIndex; + this.zoneChangeCounter.putAll(nthTargetPointer.zoneChangeCounter); } @Override public void init(Game game, Ability source) { - if (source.getTargets().size() < targetNumber) { + if (isInitialized()) { + return; + } + this.setInitialized(); + + if (source.getTargets().size() <= this.targetIndex) { + wrongTargetsUsage(source); return; } - for (UUID target : source.getTargets().get(targetIndex()).getTargets()) { + for (UUID target : source.getTargets().get(this.targetIndex).getTargets()) { Card card = game.getCard(target); if (card != null) { - addToZoneChangeCounter(target, card.getZoneChangeCounter(game)); + this.zoneChangeCounter.put(target, card.getZoneChangeCounter(game)); } } } + private void wrongTargetsUsage(Ability source) { + if (this.targetIndex > 0) { + // first target pointer is default, so must be ignored + throw new IllegalStateException("Wrong code usage: source ability miss targets setup for target pointer - " + + this.getClass().getSimpleName() + " - " + source.getClass().getSimpleName() + " - " + source); + } + } + @Override public List getTargets(Game game, Ability source) { - if (source.getTargets().size() < targetNumber) { + // can be used before effect's init (example: checking spell targets on stack before resolve like HeroicAbility) + + if (source.getTargets().size() <= this.targetIndex) { + wrongTargetsUsage(source); return emptyTargets; } - List targetIds = source.getTargets().get(targetIndex()).getTargets(); - List finalTargetIds = new ArrayList<>(targetIds.size()); - - for (UUID targetId : targetIds) { - Card card = game.getCard(targetId); - if (card != null - && getZoneChangeCounter().containsKey(targetId) - && card.getZoneChangeCounter(game) != getZoneChangeCounter().get(targetId)) { - // But no longer if new permanent is already on the battlefield - Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); - if (permanent == null || permanent.getZoneChangeCounter(game) != getZoneChangeCounter().get(targetId)) { - continue; - } + List res = new ArrayList<>(); + for (UUID targetId : source.getTargets().get(this.targetIndex).getTargets()) { + if (!isOutdatedTarget(game, targetId)) { + res.add(targetId); } - - finalTargetIds.add(targetId); } - return finalTargetIds; + return res; + } + + private boolean isOutdatedTarget(Game game, UUID targetId) { + int needZcc = this.zoneChangeCounter.getOrDefault(targetId, 0); + if (needZcc == 0) { + // any zcc (target not init yet here) + return false; + } + + // card + Card card = game.getCard(targetId); + if (card != null && card.getZoneChangeCounter(game) == needZcc) { + return false; + } + + // permanent + Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); + if (permanent != null && permanent.getZoneChangeCounter(game) == needZcc) { + return false; + } + + // TODO: if no bug reports with die triggers and new code then remove it, 2024-02-18 + // if you catch bugs then add code like if permanent.getZoneChangeCounter(game) == needZcc + 1 then return false + // old comments: + // Because if dies trigger has to trigger as permanent has already moved zone, we have to check if target + // was on the battlefield immed. before, but no longer if new permanent is already on the battlefield + + // outdated + return true; } @Override public UUID getFirst(Game game, Ability source) { - if (source.getTargets().size() < targetNumber) { + if (source.getTargets().size() <= this.targetIndex) { + wrongTargetsUsage(source); return null; } - UUID targetId = source.getTargets().get(targetIndex()).getFirstTarget(); - if (getZoneChangeCounter().containsKey(targetId)) { - Card card = game.getCard(targetId); - if (card != null && getZoneChangeCounter().containsKey(targetId) - && card.getZoneChangeCounter(game) != getZoneChangeCounter().get(targetId)) { - - // Because if dies trigger has to trigger as permanent has already moved zone, we have to check if target was on the battlefield immed. before - // but no longer if new permanent is already on the battlefield - Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); - if (permanent == null || permanent.getZoneChangeCounter(game) != zoneChangeCounter.get(targetId)) { - return null; - } - } + UUID targetId = source.getTargets().get(this.targetIndex).getFirstTarget(); + if (isOutdatedTarget(game, targetId)) { + return null; } + return targetId; - - } - - @Override - public FixedTarget getFixedTarget(Game game, Ability source) { - this.init(game, source); - UUID firstId = getFirst(game, source); - if (firstId != null) { - return new FixedTarget(firstId, game.getState().getZoneChangeCounter(firstId)); - } - - return null; } @Override public Permanent getFirstTargetPermanentOrLKI(Game game, Ability source) { - if (source.getTargets().size() < targetNumber) { + if (source.getTargets().size() < this.targetIndex) { + wrongTargetsUsage(source); return null; } + UUID targetId = source.getTargets().get(this.targetIndex).getFirstTarget(); - Permanent permanent; - UUID targetId = source.getTargets().get(targetIndex()).getFirstTarget(); - - if (getZoneChangeCounter().containsKey(targetId)) { - permanent = game.getPermanent(targetId); - if (permanent != null && permanent.getZoneChangeCounter(game) == getZoneChangeCounter().get(targetId)) { - return permanent; - } - MageObject mageObject = game.getLastKnownInformation(targetId, Zone.BATTLEFIELD, getZoneChangeCounter().get(targetId)); - if (mageObject instanceof Permanent) { - return (Permanent) mageObject; - } - + if (this.zoneChangeCounter.containsKey(targetId)) { + // need static zcc + MageObjectReference needRef = new MageObjectReference(targetId, this.zoneChangeCounter.getOrDefault(targetId, 0), game); + return game.getPermanentOrLKIBattlefield(needRef); } else { - permanent = game.getPermanent(targetId); + // need any zcc + // TODO: must research, is it used at all?! Init code must fill all static zcc data before go here + Permanent permanent = game.getPermanent(targetId); if (permanent == null) { permanent = (Permanent) game.getLastKnownInformation(targetId, Zone.BATTLEFIELD); } + return permanent; } - return permanent; } @Override public String describeTargets(Targets targets, String defaultDescription) { - return targets.size() < targetNumber ? defaultDescription : targets.get(targetIndex()).getDescription(); + if (targets.size() <= this.targetIndex) { + // TODO: need research, is it used for non setup targets ?! + return defaultDescription; + } else { + return targets.get(this.targetIndex).getDescription(); + } } @Override public boolean isPlural(Targets targets) { - return targets.size() > targetIndex() && targets.get(targetIndex()).getMaxNumberOfTargets() > 1; - } - - private int targetIndex() { - return targetNumber - 1; - } - - private Map getZoneChangeCounter() { - return zoneChangeCounter != null ? zoneChangeCounter : emptyZoneChangeCounter; - } - - private void addToZoneChangeCounter(UUID key, Integer value) { - if (zoneChangeCounter == null) { - zoneChangeCounter = new HashMap<>(); - } - getZoneChangeCounter().put(key, value); + return targets.size() > this.targetIndex && targets.get(this.targetIndex).getMaxNumberOfTargets() > 1; } } diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java index 52609e4b828..9b962fc3ad6 100644 --- a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java @@ -12,15 +12,32 @@ import java.util.UUID; public interface TargetPointer extends Serializable, Copyable { + /** + * Init dynamic targets (must save current targets zcc to fizzle it later on outdated targets) + * - one shot effects: no needs to init + * - continues effects: must use init logic (effect init on resolve or game add) + *

    + * Targets list can be accessible before effect's init. + */ void init(Game game, Ability source); + boolean isInitialized(); + + void setInitialized(); + List getTargets(Game game, Ability source); + /** + * Return first actual target id (null on outdated targets) + */ UUID getFirst(Game game, Ability source); - TargetPointer copy(); + /** + * Return first actual target data (null on outdated targets) + */ + FixedTarget getFirstAsFixedTarget(Game game, Ability source); - FixedTarget getFixedTarget(Game game, Ability source); + TargetPointer copy(); /** * Retrieves the permanent according the first targetId and diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java index 87ee02cc782..9d991af23e9 100644 --- a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java +++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java @@ -1,7 +1,11 @@ package mage.target.targetpointer; +import mage.abilities.Ability; +import mage.game.Game; + import java.util.HashMap; import java.util.Map; +import java.util.UUID; /** * @author JayDi85 @@ -11,6 +15,8 @@ public abstract class TargetPointerImpl implements TargetPointer { // Store custom data here. Use it to keep unique values for ability instances on stack (example: Gruul Ragebeast) private Map data; + private boolean initialized = false; + public TargetPointerImpl() { super(); } @@ -21,6 +27,17 @@ public abstract class TargetPointerImpl implements TargetPointer { this.data = new HashMap<>(); this.data.putAll(targetPointer.data); } + this.initialized = targetPointer.initialized; + } + + @Override + public boolean isInitialized() { + return this.initialized; + } + + @Override + public void setInitialized() { + this.initialized = true; } @Override @@ -39,4 +56,14 @@ public abstract class TargetPointerImpl implements TargetPointer { data.put(key, value); return this; } + + @Override + public final FixedTarget getFirstAsFixedTarget(Game game, Ability source) { + UUID firstId = this.getFirst(game, source); + if (firstId != null) { + return new FixedTarget(firstId, game.getState().getZoneChangeCounter(firstId)); + } + + return null; + } } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index af9fb494478..fe884e5b028 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -13,6 +13,7 @@ import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; +import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.asthought.CanPlayCardControllerEffect; import mage.abilities.effects.common.asthought.YouMaySpendManaAsAnyColorToCastTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; @@ -757,7 +758,9 @@ public final class CardUtil { } public static boolean haveEmptyName(String name) { - return name == null || name.isEmpty() || name.equals(EmptyNames.FACE_DOWN_CREATURE.toString()) || name.equals(EmptyNames.FACE_DOWN_TOKEN.toString()); + return name == null + || name.isEmpty() + || EmptyNames.isEmptyName(name); } public static boolean haveEmptyName(MageObject object) { @@ -986,6 +989,7 @@ public final class CardUtil { || text.startsWith("an ") || text.startsWith("another ") || text.startsWith("any ") + || text.startsWith("{this} ") || text.startsWith("one ")) { return text; } @@ -1081,16 +1085,26 @@ public final class CardUtil { } /** - * Put card to battlefield without resolve (for cheats and tests only) + * Put card to battlefield without resolve/ETB (for cheats and tests only) * - * @param source must be non null (if you need it empty then use fakeSourceAbility) + * @param source must be non-null (if you need it empty then use fakeSourceAbility) * @param game * @param newCard * @param player */ - public static void putCardOntoBattlefieldWithEffects(Ability source, Game game, Card newCard, Player player) { + public static void putCardOntoBattlefieldWithEffects(Ability source, Game game, Card newCard, Player player, boolean tapped) { // same logic as ZonesHandler->maybeRemoveFromSourceZone + // runtime check: must have source + if (source == null) { + throw new IllegalArgumentException("Wrong code usage: must use source ability or fakeSourceAbility"); + } + + // runtime check: must use only real cards + if (newCard instanceof PermanentCard) { + throw new IllegalArgumentException("Wrong code usage: must put to battlefield only real cards, not PermanentCard"); + } + // workaround to put real permanent from one side (example: you call mdf card by cheats) Card permCard = getDefaultCardSideForBattlefield(game, newCard); @@ -1104,15 +1118,16 @@ public final class CardUtil { permanent = new PermanentCard(permCard, player.getId(), game); } - // put onto battlefield with possible counters + // put onto battlefield with possible counters without ETB game.getPermanentsEntering().put(permanent.getId(), permanent); - permCard.checkForCountersToAdd(permanent, source, game); + permCard.applyEnterWithCounters(permanent, source, game); permanent.entersBattlefield(source, game, Zone.OUTSIDE, false); game.addPermanent(permanent, game.getState().getNextPermanentOrderNumber()); game.getPermanentsEntering().remove(permanent.getId()); - // workaround for special tapped status from test framework's command (addCard) - if (permCard instanceof PermanentCard && ((PermanentCard) permCard).isTapped()) { + // tapped status + // warning, "enters the battlefield tapped" abilities will be executed before, so don't set to false here + if (tapped) { permanent.setTapped(true); } @@ -2129,43 +2144,63 @@ public final class CardUtil { /** - * Copy image related data from one object to another (set code, card number, image number) + * Copy image related data from one object to another (set code, card number, image number, file name) * Use it in copy/transform effects */ public static void copySetAndCardNumber(MageObject targetObject, MageObject copyFromObject) { String needSetCode; String needCardNumber; + String needImageFileName; int needImageNumber; + boolean needUsesVariousArt = false; + if (copyFromObject instanceof Card) { + needUsesVariousArt = ((Card) copyFromObject).getUsesVariousArt(); + } + needSetCode = copyFromObject.getExpansionSetCode(); needCardNumber = copyFromObject.getCardNumber(); + needImageFileName = copyFromObject.getImageFileName(); needImageNumber = copyFromObject.getImageNumber(); if (targetObject instanceof Permanent) { - copySetAndCardNumber((Permanent) targetObject, needSetCode, needCardNumber, needImageNumber); + copySetAndCardNumber((Permanent) targetObject, needSetCode, needCardNumber, needImageFileName, needImageNumber, needUsesVariousArt); } else if (targetObject instanceof Token) { - copySetAndCardNumber((Token) targetObject, needSetCode, needCardNumber, needImageNumber); + copySetAndCardNumber((Token) targetObject, needSetCode, needCardNumber, needImageFileName, needImageNumber); + } else if (targetObject instanceof Card) { + copySetAndCardNumber((Card) targetObject, needSetCode, needCardNumber, needImageFileName, needImageNumber, needUsesVariousArt); } else { throw new IllegalStateException("Unsupported target object class: " + targetObject.getClass().getSimpleName()); } } - private static void copySetAndCardNumber(Permanent targetPermanent, String newSetCode, String newCardNumber, Integer newImageNumber) { + private static void copySetAndCardNumber(Permanent targetPermanent, String newSetCode, String newCardNumber, String newImageFileName, Integer newImageNumber, boolean usesVariousArt) { if (targetPermanent instanceof PermanentCard || targetPermanent instanceof PermanentToken) { targetPermanent.setExpansionSetCode(newSetCode); targetPermanent.setCardNumber(newCardNumber); + targetPermanent.setImageFileName(newImageFileName); targetPermanent.setImageNumber(newImageNumber); + targetPermanent.setUsesVariousArt(usesVariousArt); } else { throw new IllegalArgumentException("Wrong code usage: un-supported target permanent type: " + targetPermanent.getClass().getSimpleName()); } } - private static void copySetAndCardNumber(Token targetToken, String newSetCode, String newCardNumber, Integer newImageNumber) { + private static void copySetAndCardNumber(Token targetToken, String newSetCode, String newCardNumber, String newImageFileName, Integer newImageNumber) { targetToken.setExpansionSetCode(newSetCode); targetToken.setCardNumber(newCardNumber); + targetToken.setImageFileName(newImageFileName); targetToken.setImageNumber(newImageNumber); } + private static void copySetAndCardNumber(Card targetCard, String newSetCode, String newCardNumber, String newImageFileName, Integer newImageNumber, boolean usesVariousArt) { + targetCard.setExpansionSetCode(newSetCode); + targetCard.setCardNumber(newCardNumber); + targetCard.setImageFileName(newImageFileName); + targetCard.setImageNumber(newImageNumber); + targetCard.setUsesVariousArt(usesVariousArt); + } + /** * One single event can be a batch (contain multiple events) * @@ -2181,4 +2216,34 @@ public final class CardUtil { } return res; } + + /** + * Prepare card name for render in card panels, popups, etc. Can show face down status and real card name instead empty string + * + * @param imageFileName face down status or another inner image name like Morph, Copy, etc + */ + public static String getCardNameForGUI(String name, String imageFileName) { + if (imageFileName.isEmpty()) { + // normal name + return name; + } else { + // face down or inner name + return imageFileName + (name.isEmpty() ? "" : ": " + name); + } + } + + /** + * GUI related: show real name and day/night button for face down card + */ + public static boolean canShowAsControlled(Card card, UUID createdForPlayer) { + return card.getControllerOrOwnerId().equals(createdForPlayer); + } + + /** + * Ability used for information only, e.g. adds additional rule texts + */ + public static boolean isInformationAbility(Ability ability) { + return !ability.getEffects().isEmpty() + && ability.getEffects().stream().allMatch(e -> e instanceof InfoEffect); + } } diff --git a/Mage/src/main/java/mage/util/GameLog.java b/Mage/src/main/java/mage/util/GameLog.java index fc9d396cd47..4e12e9fe55d 100644 --- a/Mage/src/main/java/mage/util/GameLog.java +++ b/Mage/src/main/java/mage/util/GameLog.java @@ -2,6 +2,7 @@ package mage.util; import mage.MageObject; import mage.ObjectColor; +import mage.constants.EmptyNames; import java.util.UUID; import java.util.regex.Pattern; @@ -32,6 +33,18 @@ public final class GameLog { static final String LOG_TT_COLOR_COLORLESS = "#94A4BA"; static final String LOG_COLOR_NEUTRAL = "#F0F8FF"; // AliceBlue + private static String getNameForLogs(MageObject object) { + return getNameForLogs(object.getName()); + } + + private static String getNameForLogs(String objectName) { + if (EmptyNames.isEmptyName(objectName)) { + return EmptyNames.EMPTY_NAME_IN_LOGS; + } else { + return objectName; + } + } + public static String replaceNameByColoredName(MageObject mageObject, String text) { return replaceNameByColoredName(mageObject, text, null); } @@ -47,7 +60,7 @@ public final class GameLog { } public static String getColoredObjectName(MageObject mageObject) { - return "" + mageObject.getName() + ""; + return "" + getNameForLogs(mageObject) + ""; } public static String getColoredObjectIdName(MageObject mageObject) { @@ -58,20 +71,33 @@ public final class GameLog { return getColoredObjectIdName( mageObject.getColor(null), mageObject.getId(), - mageObject.getName(), + getNameForLogs(mageObject), String.format("[%s]", mageObject.getId().toString().substring(0, 3)), - alternativeObject == null ? null : alternativeObject.getName() + alternativeObject == null ? null : getNameForLogs(alternativeObject) + ); + } + + /** + * Create object "link" in game logs + */ + public static String getNeutralObjectIdName(String objectName, UUID objectId) { + return getColoredObjectIdName( + new ObjectColor(), + objectId, + getNameForLogs(objectName), + String.format("[%s]", objectId.toString().substring(0, 3)), + null ); } /** * Prepare html text with additional object info (can be used for card popup in GUI) * - * @param color text color of the colored part - * @param objectID object id - * @param visibleColorPart colored part, popup will be work on it + * @param color text color of the colored part + * @param objectID object id + * @param visibleColorPart colored part, popup will be work on it * @param visibleNormalPart additional part with default color - * @param alternativeName alternative name, popup will use it on unknown object id or name + * @param alternativeName alternative name, popup will use it on unknown object id or name * @return */ public static String getColoredObjectIdName(ObjectColor color, @@ -111,7 +137,11 @@ public final class GameLog { } public static String getColoredObjectIdNameForTooltip(MageObject mageObject) { - return "" + mageObject.getIdName() + ""; + return getColoredObjectIdNameForTooltip(mageObject.getColor(null), mageObject.getIdName()); + } + + public static String getColoredObjectIdNameForTooltip(ObjectColor color, String idName) { + return "" + idName + ""; } public static String getNeutralColoredText(String text) { diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index c06536acc49..6752e6c426f 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -640,7 +640,12 @@ public final class ManaUtil { } public static FilterMana getColorIdentity(Token token) { - return getColorIdentity(token.getColor(), String.join("", token.getManaCostSymbols()), token.getAbilities().getRules(token.getName()), null); + return getColorIdentity( + token.getColor(), + String.join("", token.getManaCostSymbols()), + token.getAbilities().getRules(token.getName()), + token.getBackFace() == null ? null : token.getBackFace().getCopySourceCard() + ); } public static int getColorIdentityHash(FilterMana colorIdentity) { diff --git a/Mage/src/main/java/mage/util/TournamentUtil.java b/Mage/src/main/java/mage/util/TournamentUtil.java index 3df492aad2c..3abca0e9d9a 100644 --- a/Mage/src/main/java/mage/util/TournamentUtil.java +++ b/Mage/src/main/java/mage/util/TournamentUtil.java @@ -73,7 +73,7 @@ public final class TournamentUtil { List cards = new ArrayList<>(); if (!lands.isEmpty()) { for (int i = 0; i < number; i++) { - Card land = lands.get(RandomUtil.nextInt(lands.size())).getCard(); + Card land = lands.get(RandomUtil.nextInt(lands.size())).createCard(); cards.add(land); } } diff --git a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java index 74eeabd96b6..a1950d663a0 100644 --- a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java +++ b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java @@ -3,7 +3,7 @@ package mage.util.functions; import mage.MageObject; import mage.abilities.Abilities; import mage.abilities.Ability; -import mage.abilities.keyword.MorphAbility; +import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.keyword.PrototypeAbility; import mage.cards.Card; import mage.constants.CardType; @@ -68,11 +68,11 @@ public class CopyTokenFunction { if (source instanceof PermanentCard) { // create token from non-token permanent - // morph/manifest must hide all info - if (((PermanentCard) source).isMorphed() - || ((PermanentCard) source).isManifested() - || source.isFaceDown(game)) { - MorphAbility.setPermanentToFaceDownCreature(target, (PermanentCard) source, game); + // face down must hide all info + PermanentCard sourcePermanent = (PermanentCard) source; + BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.findFaceDownType(game, sourcePermanent); + if (faceDownType != null) { + BecomesFaceDownCreatureEffect.makeFaceDownObject(game, null, target, faceDownType, null); return; } diff --git a/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java b/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java index 2c3211c1daa..a282318f028 100644 --- a/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java @@ -49,7 +49,7 @@ public class CardsPutIntoGraveyardWatcher extends Watcher { if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { cardsPutIntoGraveyardFromBattlefield.add(new MageObjectReference(((ZoneChangeEvent) event).getTarget(), game, 1)); } else { - cardsPutIntoGraveyardFromEverywhereElse.add(new MageObjectReference(((ZoneChangeEvent) event).getTarget(), game, 1)); + cardsPutIntoGraveyardFromEverywhereElse.add(new MageObjectReference(game.getCard(event.getTargetId()), game, 0)); } } diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 24a115547cc..fea5905a58f 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -7,6 +7,8 @@ # Use verify test to check it: test_checkMissingTokenData +# Inner/xmage related tokens stores in TokenRepository (copy, morph, etc) + # ALL EMBLEMS # Usage hints: # - use simple name for the emblem like Gideon @@ -124,6 +126,9 @@ |Generate|EMBLEM:CMM|Emblem Narset|||NarsetOfTheAncientWayEmblem| |Generate|EMBLEM:CMM|Emblem Nixilis|||ObNixilisOfTheBlackOathEmblem| |Generate|EMBLEM:CMM|Emblem Teferi|||TeferiTemporalArchmageEmblem| +|Generate|EMBLEM:PH17|Emblem Inzerva|||InzervaMasterOfInsightsEmblem| +|Generate|EMBLEM:LCC|Emblem Sorin|||SorinLordOfInnistradEmblem| +|Generate|EMBLEM:RVR|Emblem Domri|||DomriRadeEmblem| # ALL PLANES # Usage hints: @@ -2121,3 +2126,90 @@ |Generate|TOK:LCI|Treasure|||TreasureToken| |Generate|TOK:LCI|Vampire|||IxalanVampireToken| |Generate|TOK:LCI|Vampire Demon|||VampireDemonToken| + +# LCC +|Generate|TOK:LCC|Beast|||BeastToken| +|Generate|TOK:LCC|Bird|||BlueBirdToken| +|Generate|TOK:LCC|Blood|||BloodToken| +|Generate|TOK:LCC|Boar|||Boar2Token| +|Generate|TOK:LCC|Dinosaur|||DinosaurToken| +|Generate|TOK:LCC|Dinosaur Beast|||DinosaurBeastToken| +|Generate|TOK:LCC|Elephant|||ElephantToken| +|Generate|TOK:LCC|Frog Lizard|||FrogLizardToken| +|Generate|TOK:LCC|Merfolk|||MerfolkToken| +|Generate|TOK:LCC|Pirate|||PirateToken| +|Generate|TOK:LCC|Ragavan|||RagavanToken| +|Generate|TOK:LCC|Salamander Warrior|||SalamanderWarriorToken| +|Generate|TOK:LCC|Shapeshifter|||Shapeshifter32Token| +|Generate|TOK:LCC|Vampire|1||SorinLordOfInnistradVampireToken| +|Generate|TOK:LCC|Vampire|2||EdgarMarkovsCoffinVampireToken| + +# RVR +|Generate|TOK:RVR|Angel|1||AngelToken| +|Generate|TOK:RVR|Angel|2||AngelVigilanceToken| +|Generate|TOK:RVR|Beast|||RedGreenBeastToken| +|Generate|TOK:RVR|Bird|||BirdToken| +|Generate|TOK:RVR|Bird Illusion|||BirdIllusionToken| +|Generate|TOK:RVR|Centaur|||CentaurToken| +|Generate|TOK:RVR|Dragon|||UtvaraHellkiteDragonToken| +|Generate|TOK:RVR|Elf Knight|||ElfKnightToken| +|Generate|TOK:RVR|Goblin|1||GoblinToken| +|Generate|TOK:RVR|Goblin|2||RakdosGuildmageGoblinToken| +|Generate|TOK:RVR|Rhino|||RhinoToken| +|Generate|TOK:RVR|Saproling|||SaprolingToken| +|Generate|TOK:RVR|Soldier|||SoldierTokenWithHaste| +|Generate|TOK:RVR|Sphinx|||WardenSphinxToken| +|Generate|TOK:RVR|Spirit|1||SpiritWhiteToken| +|Generate|TOK:RVR|Spirit|2||WhiteBlackSpiritToken| +|Generate|TOK:RVR|Voja|||VojaToken| +|Generate|TOK:RVR|Wurm|||WurmToken| +|Generate|TOK:RVR|Zombie|||ZombieToken| + +# MKM +|Generate|TOK:MKM|Bat|||BatToken| +|Generate|TOK:MKM|Clue|1||ClueArtifactToken| +|Generate|TOK:MKM|Clue|2||ClueArtifactToken| +|Generate|TOK:MKM|Clue|3||ClueArtifactToken| +|Generate|TOK:MKM|Clue|4||ClueArtifactToken| +|Generate|TOK:MKM|Clue|5||ClueArtifactToken| +|Generate|TOK:MKM|Detective|||DetectiveToken| +|Generate|TOK:MKM|Dog|||WhiteDogToken| +|Generate|TOK:MKM|Goblin|||GoblinToken| +|Generate|TOK:MKM|Human|||HumanToken| +|Generate|TOK:MKM|Imp|||ImpToken| +|Generate|TOK:MKM|Merfolk|||MerfolkToken| +|Generate|TOK:MKM|Ooze|||OozeTrampleToken| +|Generate|TOK:MKM|Plant|||PlantToken| +|Generate|TOK:MKM|Skeleton|||SkeletonToken2| +|Generate|TOK:MKM|Spider|||IzoniSpiderToken| +|Generate|TOK:MKM|Spirit|||WhiteBlackSpiritToken| +|Generate|TOK:MKM|Thopter|1||Thopter00ColorlessToken| +|Generate|TOK:MKM|Thopter|2||ThopterColorlessToken| +|Generate|TOK:MKM|Voja Fenstalker|||TolsimirMidnightsLightToken| + +# MKC +|Generate|TOK:MKC|Cat|||GreenCat2Token| +|Generate|TOK:MKC|Clue|||ClueArtifactToken| +|Generate|TOK:MKC|Construct|||StoneIdolToken| +|Generate|TOK:MKC|Drake|||DrakeToken| +|Generate|TOK:MKC|Eldrazi|||EldraziToken| +|Generate|TOK:MKC|Food|||FoodToken| +|Generate|TOK:MKC|Gold|||GoldToken| +|Generate|TOK:MKC|Human Soldier|||HumanSoldierToken| +|Generate|TOK:MKC|Insect|1||InsectToken| +|Generate|TOK:MKC|Insect|2||InsectDeathToken| +|Generate|TOK:MKC|Kobolds of Kher Keep|||KherKeepKoboldToken| +|Generate|TOK:MKC|Koma's Coil|||KomasCoilToken| +|Generate|TOK:MKC|Lightning Rager|||LightningRagerToken| +|Generate|TOK:MKC|Ogre|||OgreToken| +|Generate|TOK:MKC|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:MKC|Rhino Warrior|||RhinoWarriorToken| +|Generate|TOK:MKC|Salamander Warrior|||SalamanderWarriorToken| +|Generate|TOK:MKC|Saproling|||SaprolingToken| +|Generate|TOK:MKC|Snake|||SnakeToken| +|Generate|TOK:MKC|Soldier|||SoldierToken| +|Generate|TOK:MKC|Spirit|||SpiritWhiteToken| +|Generate|TOK:MKC|Tentacle|||TentacleToken| +|Generate|TOK:MKC|Tiny|||TinyToken| +|Generate|TOK:MKC|Treasure|||TreasureToken| +|Generate|TOK:MKC|Zombie|||ZombieToken| diff --git a/Utils/known-sets.txt b/Utils/known-sets.txt index be8abcfebf2..ef5c12414ea 100644 --- a/Utils/known-sets.txt +++ b/Utils/known-sets.txt @@ -10,12 +10,15 @@ Apocalypse|Apocalypse| Archenemy|Archenemy| Archenemy: Nicol Bolas|ArchenemyNicolBolas| Arabian Nights|ArabianNights| +Arena Beginner Set|ArenaBeginnerSet| Arena League|ArenaLeague| Asia Pacific Land Program|AsiaPacificLandProgram| +Assassin's Creed|AssassinsCreed| Avacyn Restored|AvacynRestored| Battlebond|Battlebond| Battle for Zendikar|BattleForZendikar| Betrayers of Kamigawa|BetrayersOfKamigawa| +Bloomburrow|Bloomburrow| Born of the Gods|BornOfTheGods| Champions of Kamigawa|ChampionsOfKamigawa| Champs|Champs| @@ -177,6 +180,7 @@ Mirrodin|Mirrodin| Mirrodin Besieged|MirrodinBesieged| Modern Horizons|ModernHorizons| Modern Horizons 2|ModernHorizons2| +Modern Horizons 3|ModernHorizons3| Modern Masters|ModernMasters| Modern Masters 2015|ModernMasters2015| Modern Masters 2017|ModernMasters2017| @@ -190,6 +194,8 @@ Ninth Edition|NinthEdition| Oath of the Gatewatch|OathOfTheGatewatch| Odyssey|Odyssey| Onslaught|Onslaught| +Outlaws of Thunder Junction|OutlawsOfThunderJunction| +The Big Score|TheBigScore| Phyrexia: All Will Be One|PhyrexiaAllWillBeOne| Phyrexia: All Will Be One Commander|PhyrexiaAllWillBeOneCommander| Planar Chaos|PlanarChaos| @@ -215,6 +221,7 @@ Saviors of Kamigawa|SaviorsOfKamigawa| Scars of Mirrodin|ScarsOfMirrodin| Scourge|Scourge| Secret Lair Drop|SecretLairDrop| +Secret Lair Showdown|SecretLairShowdown| Seventh Edition|SeventhEdition| Shadowmoor|Shadowmoor| Shadows over Innistrad|ShadowsOverInnistrad| @@ -222,6 +229,7 @@ Shards of Alara|ShardsOfAlara| Special Guests|SpecialGuests| Starter 1999|Starter1999| Starter 2000|Starter2000| +Starter Commander Decks|StarterCommanderDecks| Star Wars|StarWars| Streets of New Capenna|StreetsOfNewCapenna| New Capenna Commander|NewCapennaCommander| @@ -265,4 +273,4 @@ WPN Gateway|WPNAndGatewayPromos| Zendikar|Zendikar| Zendikar Expeditions|ZendikarExpeditions| Zendikar Rising|ZendikarRising| -Zendikar Rising Commander|ZendikarRisingCommander| \ No newline at end of file +Zendikar Rising Commander|ZendikarRisingCommander| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 77493e3cd84..0395064a917 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -4324,12 +4324,12 @@ Silumgar's Scorn|Dragons of Tarkir|78|U|{U}{U}|Instant|||As an additional cost t Skywise Teachings|Dragons of Tarkir|79|U|{3}{U}|Enchantment|||Whenever you cast a noncreature spell, you may pay {1}{U}. If you do, put a 2/2 blue Djinn Monk creature token with flying onto the battlefield.| Stratus Dancer|Dragons of Tarkir|80|R|{1}{U}|Creature - Djinn Monk|2|1|Flying$Megamorph {1}{U} You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time from its megamorph cost and put a +1/+1 counter on it.)| Taigam's Strike|Dragons of Tarkir|81|C|{3}{U}|Sorcery|||Target creature gets +2/+0 until end of turn and can't be blocked this turn.$Rebound (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)| -Updraft Elemental|Dragons of Tarkir|82|C|{2}{U}|Creature - Elemental|1|4|Flying|| +Updraft Elemental|Dragons of Tarkir|82|C|{2}{U}|Creature - Elemental|1|4|Flying| Void Squall|Dragons of Tarkir|83|U|{4}{U}|Sorcery|||Return target nonland permanent to its owner's hand.$Rebound (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.)| Youthful Scholar|Dragons of Tarkir|84|U|{3}{U}|Creature - Human Wizard|2|2|When Youthful Scholar dies, draw two cards.| Zephyr Scribe|Dragons of Tarkir|85|C|{2}{U}|Creature - Human Monk|2|1|{U}, {T}: Draw a card, then discard a card.$Whenever you cast a noncreature spell, untap Zephyr Scribe.| Acid-Spewer Dragon|Dragons of Tarkir|86|U|{5}{B}|Creature - Dragon|3|3|Flying, deathtouch$Megamorph {5}{B}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its megamorph cost and put a +1/+1 counter on it.)$When Acid-Spewer Dragon is turned face up, put a +1/+1 counter on each other Dragon creature you control.| -Ambuscade Shaman|Dragons of Tarkir|87|U}{2}{B}|Creature - Orc Shaman|2|2|Whenever Ambuscade Shaman or another creature enters the battlefield under your control, that creature gets +2/+2 until end of turn.$Dash {3}{B} (You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step.)| +Ambuscade Shaman|Dragons of Tarkir|87|U|{2}{B}|Creature - Orc Shaman|2|2|Whenever Ambuscade Shaman or another creature enters the battlefield under your control, that creature gets +2/+2 until end of turn.$Dash {3}{B} (You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step.)| Blood-Chin Fanatic|Dragons of Tarkir|88|R|{1}{B}{B}|Creature - Orc Warrior|3|3|{1}{B}, Sacrifice another Warrior creature: Target player loses X life and you gain X life, where X is the sacrificed creature's power.| Blood-Chin Rager|Dragons of Tarkir|89|U|{1}{B}|Creature - Human Warrior|2|2|Whenever Blood-Chin Rager attacks, each Warrior creature you control can't be blocked this turn except by two or more creatures.| Butcher's Glee|Dragons of Tarkir|90|C|{2}{B}|Instant|||Target creature gets +3/+0 and gains lifelink until end of turn. Regenerate it. (Damage dealt by a creature with lifelink also causes its controller to gain that much life.)| @@ -4365,7 +4365,7 @@ Sibsig Icebreakers|Dragons of Tarkir|119|C|{2}{B}|Creature - Zombie|2|3|When Sib Sidisi, Undead Vizier|Dragons of Tarkir|120|R|{3}{B}{B}|Legendary Creature - Zombie Naga|4|6|Deathtouch$Exploit (When this creature enters the battlefield, you may sacrifice a creature.)$When Sidisi, Undead Vizier exploits a creature, you may search your library for a card, put it into your hand, then shuffle your library.| Silumgar Assassin|Dragons of Tarkir|121|R|{1}{B}|Creature - Human Assassin|2|1|Creatures with power greater than Silumgar Assassin's power can't block it.$Megamorph {2}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up at any time for its megamorph cost and put a +1/+1 counter on it.)$When Silumgar Assassin is turned face up, destroy target creature with power 3 or less an opponent controls.| Silumgar Butcher|Dragons of Tarkir|122|C|{4}{B}|Creature - Zombie Djinn|3|3|Exploit (When this creature enters the battlefield, you may sacrifice a creature.)$When Silumgar Butcher exploits a creature, target creature gets -3/-3 until end of turn.| -Ukud Cobra|Dragons of Tarkir|123|U|{3}{B}|Creature - Snake|2|5|Deathtough|| +Ukud Cobra|Dragons of Tarkir|123|U|{3}{B}|Creature - Snake|2|5|Deathtouch| Ultimate Price|Dragons of Tarkir|124|U|{1}{B}|Instant|||Destroy target monocolored creature.| Virulent Plague|Dragons of Tarkir|125|U|{2}{B}|Enchantment|||Creature tokens get -2/-2| Vulturous Aven|Dragons of Tarkir|126|C|{3}{B}|Creature - Bird Shaman|2|3|Flying$Exploit (When this creature enters the battlefield, you may sacrifice a creature.)$When Vulturous Aven explots a creature, you draw two cards and you lose 2 life.| @@ -5603,7 +5603,7 @@ Woodborn Behemoth|Duel Decks: Nissa vs. Ob Nixilis|26|U|{3}{G}{G}|Creature - Ele Fertile Thicket|Duel Decks: Nissa vs. Ob Nixilis|27|C||Land|||Fertile Thicket enters the battlefield tapped.$When Fertile Thicket enters the battlefield, you may look at the top five cards of your library. If you do, reveal up to one basic land card from among them, then put that card on top of your library and the rest on the bottom in any order.${T}: Add {G}.| Khalni Garden|Duel Decks: Nissa vs. Ob Nixilis|28|C||Land|||Khalni Garden enters the battlefield tapped.$When Khalni Garden enters the battlefield, put a 0/1 green Plant creature token onto the battlefield.${tap}: Add {G}.| Mosswort Bridge|Duel Decks: Nissa vs. Ob Nixilis|29|R||Land|||Hideaway (This land enters the battlefield tapped. When it does, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library.)${tap}: Add {G}.${G}, {tap}: You may play the exiled card without paying its mana cost if creatures you control have total power 10 or greater.| -Treetop Village|Duel Decks: Nissa vs. Ob Nixilis|30|U|||Land|||Treetop Village enters the battlefield tapped.${tap}: Add {G}.${1}{G}: Treetop Village becomes a 3/3 green Ape creature with trample until end of turn. It's still a land. (If it would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker.)| +Treetop Village|Duel Decks: Nissa vs. Ob Nixilis|30|U||Land|||Treetop Village enters the battlefield tapped.${tap}: Add {G}.${1}{G}: Treetop Village becomes a 3/3 green Ape creature with trample until end of turn. It's still a land. (If it would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker.)| Forest|Duel Decks: Nissa vs. Ob Nixilis|31|L||Basic Land - Forest|||G| Forest|Duel Decks: Nissa vs. Ob Nixilis|32|L||Basic Land - Forest|||G| Forest|Duel Decks: Nissa vs. Ob Nixilis|33|L||Basic Land - Forest|||G| @@ -7930,13 +7930,13 @@ Whipcorder|Friday Night Magic|40|U|{W}{W}|Creature Human Soldier Rebel|2|2|{W} Sparksmith|Friday Night Magic|41|U|{1}{R}|Creature Goblin|1|1|{tap}: Sparksmith deals X damage to target creature and X damage to you, where X is the number of Goblins on the battlefield.| Krosan Tusker|Friday Night Magic|42|U|{5}{G}{G}|Creature Boar Beast|6|5|Cycling {2}{G} ({2}{G}, Discard this card: Draw a card.)$When you cycle Krosan Tusker, you may search your library for a basic land card, reveal that card, put it into your hand, then shuffle your library.| Withered Wretch|Friday Night Magic|43|U|{B}{B}|Creature Zombie Cleric|2|2|{1}: Exile target card from a graveyard.| -Willbender|Friday Night Magic|44|U|{1}{U}|Creature Human Wizard|Morph {1}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)$When Willbender is turned face up, change the target of target spell or ability with a single target.| +Willbender|Friday Night Magic|44|U|{1}{U}|Creature Human Wizard|1|2|Morph {1}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)$When Willbender is turned face up, change the target of target spell or ability with a single target.| Slice and Dice|Friday Night Magic|45|U|{4}{R}{R}|Sorcery|||Slice and Dice deals 4 damage to each creature.$Cycling {2}{R} ({2}{R}, Discard this card: Draw a card.)$When you cycle Slice and Dice, you may have it deal 1 damage to each creature.| Silver Knight|Friday Night Magic|46|U|{W}{W}|Creature Human Knight|2|2|First strike$Protection from red| Krosan Warchief|Friday Night Magic|47|U|{2}{G}|Creature Beast|2|2|Beast spells you cast cost {1} less to cast.${1}{G}: Regenerate target Beast.| Lightning Rift|Friday Night Magic|48|U|{1}{R}|Enchantment|||Whenever a player cycles a card, you may pay {1}. If you do, Lightning Rift deals 2 damage to any target.| Carrion Feeder|Friday Night Magic|49|U|{B}|Creature Zombie|1|1|Carrion Feeder can't block.$acrifice a creature: Put a +1/+1 counter on Carrion Feeder.| -Treetop Village|Friday Night Magic|50|U|||Land|||Treetop Village enters the battlefield tapped.${tap}: Add {G}.${1}{G}: Treetop Village becomes a 3/3 green Ape creature with trample until end of turn. It's still a land. (If it would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker.)| +Treetop Village|Friday Night Magic|50|U||Land|||Treetop Village enters the battlefield tapped.${tap}: Add {G}.${1}{G}: Treetop Village becomes a 3/3 green Ape creature with trample until end of turn. It's still a land. (If it would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker.)| Accumulated Knowledge|Friday Night Magic|51|U|{1}{U}|Instant|||Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards.| Avalanche Riders|Friday Night Magic|52|U|{3}{R}|Creature Human Nomad|2|2|Haste$Echo {3}{R} (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)$When Avalanche Riders enters the battlefield, destroy target land.| Reanimate|Friday Night Magic|53|U|{B}|Sorcery|||Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost.| @@ -8048,7 +8048,7 @@ Judge's Familiar|Friday Night Magic|156|U|{WU}|Creature - Bird|1|1|Flying$Sacrif Izzet Charm|Friday Night Magic|157|U|{U}{R}|Instant|||Choose one Counter target noncreature spell unless its controller pays {2}; or Izzet Charm deals 2 damage to target creature; or draw two cards, then discard two cards.| Rakdos Cackler|Friday Night Magic|158|U|{BR}|Creature - Devil|1|1|Unleash (You may have this creature enter the battlefield with a +1/+1 counter on it. It can't block as long as it has a +1/+1 counter on it.)| Dimir Charm|Friday Night Magic|159|U|{U}{B}|Instant|||Choose one Counter target sorcery spell; or destroy target creature with power 2 or less; or look at the top three cards of target player's library, then put one back and the rest into that player's graveyard.| -Experiment One|Friday Night Magic|160|U|{G}|Creature - Human Oooze|1|1|Evolve (Whenever a creature enters the battlefield under your control, if that creature has greater power or toughness than this creature, put a +1/+1 counter on this creature.)|Remove two +1/+1 counters from Experiment One: Regenerate Experiment One.| +Experiment One|Friday Night Magic|160|U|{G}|Creature - Human Oooze|1|1|Evolve (Whenever a creature enters the battlefield under your control, if that creature has greater power or toughness than this creature, put a +1/+1 counter on this creature.)$Remove two +1/+1 counters from Experiment One: Regenerate Experiment One.| Ghor-Clan Rampager|Friday Night Magic|161|U|{2}{R}{G}|Creature - Beast|4|4|Trample$Bloodrush {R}{G}, Discard Ghor-Clan Rampager: Target attacking creature gets +4/+4 and gains trample until end of turn.| Grisly Salvage|Friday Night Magic|162|U|{B}{G}|Instant|||Reveal the top five cards of your library. You may put a creature or land card from among them into your hand. Put the rest into your graveyard.| Sin COllector|Friday Night Magic|163|U|{1}{W}{B}|Creature - Human Cleric|2|1|When Sin Collector enters the battlefield, target opponent reveals their hand. You choose an instant or sorcery card from it and exile that card.| @@ -8229,7 +8229,7 @@ Fact or Fiction|From the Vault: Twenty|9|M|{3}{U}|Instant||| Reveal the top five Chainer's Edict|From the Vault: Twenty|10|M|{1}{B}|Sorcery|||Target player sacrifices a creature.$Flashback {5}{B}{B} (You may cast this card from your graveyard for its flashback cost. Then exile it.)| Akroma's Vengeance|From the Vault: Twenty|11|M|{4}{W}{W}|Sorcery|||Destroy all artifacts, creatures, and enchantments.$Cycling {3} ({3}, Discard this card: Draw a card.)| Gilded Lotus|From the Vault: Twenty|12|M|{5}|Artifact|||{T}: Add three mana of any one color.| -Ink-Eyes, Servant of Oni|From the Vault: Twenty|13|M|{4}{B}{B}|Legendary Creature - Rat Ninja|5|4|Ninjutsu {3}{B}{B} ({3}{B}{B}, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking.)|Whenever Ink-Eyes, Servant of Oni deals combat damage to a player, you may put target creature card from that player's graveyard onto the battlefield under your control.${1}{B}: Regenerate Ink-Eyes.| +Ink-Eyes, Servant of Oni|From the Vault: Twenty|13|M|{4}{B}{B}|Legendary Creature - Rat Ninja|5|4|Ninjutsu {3}{B}{B} ({3}{B}{B}, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking.)$Whenever Ink-Eyes, Servant of Oni deals combat damage to a player, you may put target creature card from that player's graveyard onto the battlefield under your control.${1}{B}: Regenerate Ink-Eyes.| Char|From the Vault: Twenty|14|M|{2}{R}|Instant|||Char deals 4 damage to any target and 2 damage to you.| Venser, Shaper Savant|From the Vault: Twenty|15|M|{2}{U}{U}|Legendary Creature - Human Wizard|2|2|Flash (You may cast this spell any time you could cast an instant.)$When Venser, Shaper Savant enters the battlefield, return target spell or permanent to its owner's hand.| Chameleon Colossus|From the Vault: Twenty|16|M|{2}{G}{G}|Creature - Shapeshifter|4|4|Changeling (This card is every creature type at all times.)$Protection from black${2}{G}{G}: Chameleon Colossus gets +X/+X until end of turn, where X is its power.| @@ -20118,7 +20118,7 @@ Grim Lavamancer|Premium Deck Series: Fire & Lightning|1|R|{R}|Creature - Human W Jackal Pup|Premium Deck Series: Fire & Lightning|2|U|{R}|Creature - Hound|2|1|Whenever Jackal Pup is dealt damage, it deals that much damage to you.| Mogg Fanatic|Premium Deck Series: Fire & Lightning|3|U|{R}|Creature - Goblin|1|1|Sacrifice Mogg Fanatic: Mogg Fanatic deals 1 damage to any target.| Spark Elemental|Premium Deck Series: Fire & Lightning|4|U|{R}|Creature - Elemental|3|1|Trample, haste (If this creature would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker. This creature can attack and {T} as soon as it comes under your control.)$At the beginning of the end step, sacrifice Spark Elemental.| -Figure of Destiny|Premium Deck Series: Fire & Lightning|5|R|{RW}|Creature - Kithkin|1|1|{RW}: Figure of Destiny becomes a Kithkin Spirit with base power and toughness 2/2.${RW}{RW}{RW}: If Figure of Destiny is a Spirit, it becomes a Kithkin Spirit Warrior with base power and toughness 4/4.${RW}{RW}{RW}{RW}{RW}{RW}: If Figure of Destiny is a Warrior, it becomes a Kithkin Spirit Warrior Avatar with base power and toughness 8/8, flying, and first strike.|Hellspark Elemental|Premium Deck Series: Fire & Lightning|6|U|{1}{R}|Creature - Elemental|3|1| +Figure of Destiny|Premium Deck Series: Fire & Lightning|5|R|{RW}|Creature - Kithkin|1|1|{RW}: Figure of Destiny becomes a Kithkin Spirit with base power and toughness 2/2.${RW}{RW}{RW}: If Figure of Destiny is a Spirit, it becomes a Kithkin Spirit Warrior with base power and toughness 4/4.${RW}{RW}{RW}{RW}{RW}{RW}: If Figure of Destiny is a Warrior, it becomes a Kithkin Spirit Warrior Avatar with base power and toughness 8/8, flying, and first strike.| Hellspark Elemental|Premium Deck Series: Fire & Lightning|6|U|{1}{R}|Creature - Elemental|3|1|Trample, haste$At the beginning of the end step, sacrifice Hellspark Elemental.$Unearth {1}{R} ({1}{R}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)| Keldon Marauders|Premium Deck Series: Fire & Lightning|7|C|{1}{R}|Creature - Human Warrior|3|3|Vanishing 2 (This permanent enters the battlefield with two time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)$When Keldon Marauders enters the battlefield or leaves the battlefield, it deals 1 damage to target player.| Mogg Flunkies|Premium Deck Series: Fire & Lightning|8|C|{1}{R}|Creature - Goblin|3|3|Mogg Flunkies can't attack or block alone.| @@ -24112,7 +24112,7 @@ Craven Giant|Tempest Remastered|128|C|{2}{R}|Creature - Giant|4|1|Craven Giant c Deadshot|Tempest Remastered|129|U|{3}{R}|Sorcery|||Tap target creature. It deals damage equal to its power to another target creature.| Fanning the Flames|Tempest Remastered|130|R|{X}{R}{R}|Sorcery|||Buyback {3} (You may pay an additional {3} as you cast this spell. If you do, put this card into your hand as it resolves.)$Fanning the Flames deals X damage to any target.| Flame Wave|Tempest Remastered|131|R|{3}{R}{R}{R}{R}|Sorcery|||Flame Wave deals 4 damage to target player and each creature they control.| -Flowstone Blade|Tempest Remastered|132|C|{R}|Enchantment - Aura|||Enchant creature|||{R}: Enchanted creature gets +1/-1 until end of turn.| +Flowstone Blade|Tempest Remastered|132|C|{R}|Enchantment - Aura|||Enchant creature${R}: Enchanted creature gets +1/-1 until end of turn.| Flowstone Mauler|Tempest Remastered|133|U|{4}{R}{R}|Creature - Beast|4|5|Trample${R}: Flowstone Mauler gets +1/-1 until end of turn.| Flowstone Wyvern|Tempest Remastered|134|U|{3}{R}{R}|Creature - Drake|3|3|Flying${R}: Flowstone Wyvern gets +2/-2 until end of turn.| Furnace Brood|Tempest Remastered|135|C|{3}{R}|Creature - Elemental|3|3|{R}: Target creature can't be regenerated this turn.| @@ -28897,7 +28897,7 @@ Latch Seeker|Game Day|18|U|{1}{U}{U}|Creature - Spirit|3|1|Latch Seeker can't be Killing Wave|Game Day|19|R|{X}{B}|Sorcery|||For each creature, its controller sacrifices it unless they pay X life.| Magmaquake|Game Day|20|R|{X}{R}{R}|Instant|||Magmaquake deals X damage to each creature without flying and each planeswalker.| Mwonvuli Beast Tracker|Game Day|21|U|{1}{G}{G}|Creature - Human Scout|2|1|When Mwonvuli Beast Tracker enters the battlefield, search your library for a creature card with deathtouch, hexproof, reach, or trample and reveal it. Shuffle your library and put that card on top of it.| -Cryptborn Horror|Game Day|22|R|{1}{BR}{BR}||Creature - Horror|0|0|Trample$Cryptborn Horror enters the battlefield with X +1/+1 counters on it, where X is the total life lost by your opponents this turn.| +Cryptborn Horror|Game Day|22|R|{1}{BR}{BR}|Creature - Horror|0|0|Trample$Cryptborn Horror enters the battlefield with X +1/+1 counters on it, where X is the total life lost by your opponents this turn.| Dryad Militant|Game Day|23|U|{GW}|Creature - Dryad Soldier|2|1|If an instant or sorcery card would be put into a graveyard from anywhere, exile it instead.| Firemane Avenger|Game Day|24|R|{2}{W}{R}|Creature - Angel|3|3|Flying$Battalion Whenever Firemane Avenger and at least two other creatures attack, Firemane Avenger deals 3 damage to any target and you gain 3 life.| Zameck Guildmage|Game Day|25|U|{U}{G}|Creature - Elf Wizard|2|2|{G}{U}: This turn, each creature you control enters the battlefield with an additional +1/+1 counter on it.${G}{U}, Remove a +1/+1 counter from a creature you control: Draw a card.| @@ -29335,7 +29335,7 @@ Hound of the Farbogs|Shadows over Innistrad|117|C|{4}{B}|Creature - Zombie Hound Indulgent Aristocrat|Shadows over Innistrad|118|U|{B}|Creature - Vampire|1|1|Lifelink${2}, Sacrifice a creature: Put a +1/+1 counter on each Vampire you control.| Kindly Stranger|Shadows over Innistrad|119a|U|{2}{B}|Creature - Human|2|3|Delirium — {2}{B}: Transform Kindly Stranger. Activate this ability only if there are four or more card types among cards in your graveyard.| Demon-Possessed Witch|Shadows over Innistrad|119b|U||Creature - Human Shaman|4|3|When this creature transforms into Demon-Possessed Witch, you may destroy target creature.| -Liliana's Indignation|Shadows over Innistrad|120|U|{X}{B}|Sorcery|Put the top X cards of your library into your graveyard. Target player loses 2 life for each creature card put into your graveyard this way.| +Liliana's Indignation|Shadows over Innistrad|120|U|{X}{B}|Sorcery|||Put the top X cards of your library into your graveyard. Target player loses 2 life for each creature card put into your graveyard this way.| Macabre Waltz|Shadows over Innistrad|121|C|{1}{B}|Sorcery|||Return up to two target creature cards from your graveyard to your hand, then discard a card.| Markov Dreadknight|Shadows over Innistrad|122|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| Merciless Resolve|Shadows over Innistrad|123|C|{2}{B}|Instant|||As an additional cost to cast Merciless Resolve, sacrifice a creature or land.$Draw two cards.| @@ -31098,7 +31098,7 @@ Traitorous Instinct|Modern Masters 2017|114|C|{3}{R}|Sorcery|||Gain control of t Vithian Stinger|Modern Masters 2017|115|U|{2}{R}|Creature - Human Shaman|0|1|{T}: Vithian Stinger deals 1 damage to any target.$Unearth {1}{R} ({1}{R}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)| Zealous Conscripts|Modern Masters 2017|116|R|{4}{R}|Creature - Human Warrior|3|3|Haste$When Zealous Conscripts enters the battlefield, gain control of target permanent until end of turn. Untap that permanent. It gains haste until end of turn.| Arachnus Spinner|Modern Masters 2017|117|U|{5}{G}|Creature - Spider|5|7|Reach$Tap an untapped Spider you control: Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you seach your library this way, shuffle it.| -Arachnus Web|Modern Masters 2017|118|C|{2}{G}|Enchant creature$Enchanted creature can't attack or block, and its activated abilities can't be activated.$At the beginning of the end step, if enchanted creature's power is 4 or greater, destroy Arachnus Web.| +Arachnus Web|Modern Masters 2017|118|C|{2}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block, and its activated abilities can't be activated.$At the beginning of the end step, if enchanted creature's power is 4 or greater, destroy Arachnus Web.| Avacyn's Pilgrim|Modern Masters 2017|119|C|{G}|Creature - Human Monk|1|1|{T}: Add {W}.| Baloth Cage Trap|Modern Masters 2017|120|U|{3}{G}{G}|Instant - Trap|||If an opponent had an artifact enter the battlefield under their control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost.$Create a 4/4 green Beast creature token.| Call of the Herd|Modern Masters 2017|121|R|{2}{G}|Sorcery|||Create a 3/3 green Elephant creature token.$Flashback {3}{G}| @@ -31209,7 +31209,7 @@ Selesnya Signet|Modern Masters 2017|226|U|{2}|Artifact|||{1}, {T}: Add {G}{W}.| Simic Signet|Modern Masters 2017|227|U|{2}|Artifact|||{1}, {T}: Add {G}{U}.| Arcane Sanctum|Modern Masters 2017|228|U||Land|||Arcane Sanctum enters the battlefield tapped.${T}: Add {W}, {U}, or {B}.| Arid Mesa|Modern Masters 2017|229|R||Land|||{T}, Pay 1 life, Sacrifice Arid Mesa: Search your library for a Mountain or Plains card and put it onto the battlefield. Then shuffle your library.| -Azorius Guildgate|Modern Masters 2017|230|C||Land - Gate||||Azorius Guildgate enters the battlefield tapped.${T}: Add {W} or {U}.| +Azorius Guildgate|Modern Masters 2017|230|C||Land - Gate|||Azorius Guildgate enters the battlefield tapped.${T}: Add {W} or {U}.| Boros Guildgate|Modern Masters 2017|231|C||Land - Gate|||Boros Guildgate enters the battlefield tapped.${T}: Add {R} or {W}.| Cavern of Souls|Modern Masters 2017|232|M||Land|||As Cavern of Souls enters the battlefield, choose a creature type.${T}: Add {C}.${T}: Add one mana of any color. Spend this mana only to cast a creature spell of the chosen type, and that spell can't be countered.| Crumbling Necropolis|Modern Masters 2017|233|U||Land|||Crumbling Necropolis enters the battlefield tapped.${T}: Add {U}, {B}, or {R}.| @@ -31463,7 +31463,7 @@ Fight|Amonkhet|220b|R|{3}{G}|Sorcery|||Target creature you control fights target Failure|Amonkhet|221a|R|{1}{U}|Instant|||Return target spell to its owner's hand.| Comply|Amonkhet|221b|R|{W}|Sorcery|||Aftermath$Choose a card name. Until your next turn, your opponents can't cast spells with the chosen name.| Rags|Amonkhet|222a|R|{2}{B}{B}|Sorcery|||All creatures get -2/-2 until end of turn.| -Riches|Amonkhet|222b|R|{5}{U}{U}|Sorcery|Aftermath$Each opponent chooses a creature they control. You gain control of those creatures.| +Riches|Amonkhet|222b|R|{5}{U}{U}|Sorcery|||Aftermath$Each opponent chooses a creature they control. You gain control of those creatures.| Cut|Amonkhet|223a|R|{1}{R}|Sorcery|||Cut deals 4 damage to target creature.| Ribbons|Amonkhet|223b|R|{X}{B}{B}|Sorcery|||Aftermath$Each opponent loses X life.| Heaven|Amonkhet|224a|R|{X}{G}|Instant|||Heaven deals X damage to each creature with flying.| @@ -31793,7 +31793,7 @@ Thousand-Year Elixir|Commander Anthology|236|R|{3}|Artifact|||You may activate a Thunderstaff|Commander Anthology|237|U|{3}|Artifact|||As long as Thunderstaff is untapped, if a creature would deal combat damage to you, prevent 1 of that damage.${2}, {tap}: Attacking creatures get +1/+0 until end of turn.| Akoum Refuge|Commander Anthology|238|U||Land|||Akoum Refuge enters the battlefield tapped.$When Akoum Refuge enters the battlefield, you gain 1 life.${tap}: Add {B} or {R}.| Azorius Chancery|Commander Anthology|239|C||Land|||Azorius Chancery enters the battlefield tapped.$When Azorius Chancert enters the battlefield, return a land you control to its owner's hand.${T}: Add {W}{U}.| -Azorius Guildgate|Commander Anthology|240|C||Land - Gate||||Azorius Guildgate enters the battlefield tapped.${T}: Add {W} or {U}.| +Azorius Guildgate|Commander Anthology|240|C||Land - Gate|||Azorius Guildgate enters the battlefield tapped.${T}: Add {W} or {U}.| Bant Panorama|Commander Anthology|241|C||Land|||{tap}: Add {C}.${1}, {tap}, Sacrifice Bant Panorama: Search your library for a basic Forest, Plains, or Island card and put it onto the battlefield tapped. Then shuffle your library.| Barren Moor|Commander Anthology|242|C||Land|||Barren Moor enters the battlefield tapped.${tap}: Add {B}.$Cycling {B} ({B}, Discard this card: Draw a card.)| Bojuka Bog|Commander Anthology|243|C||Land|||Bojuka Bog enters the battlefield tapped.$When Bojuka Bog enters the battlefield, exile all cards from target player's graveyard.${tap}: Add {B}.| @@ -31885,7 +31885,7 @@ Hyena Umbra|Planechase Anthology|8|C|{W}|Enchantment|||Enchant creature$Enchante Kor Spiritdancer|Planechase Anthology|9|R|{1}{W}|Creature - Kor Wizard|0|2|Kor Spiritdancer gets +2/+2 for each Aura attached to it.$Whenever you cast an Aura spell, you may draw a card.| Mammoth Umbra|Planechase Anthology|10|U|{4}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +3/+3 and has vigilance.$Totem armor| Sigil of the Empty Throne|Planechase Anthology|11|R|{3}{W}{W}|Enchantment|||Whenever you cast an enchantment spell, create a 4/4 white Angel creature token with flying.| -Spirit Mantle|Planechase Anthology|12|U||{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1 and has protection from creatures.| +Spirit Mantle|Planechase Anthology|12|U|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1 and has protection from creatures.| Three Dreams|Planechase Anthology|13|R|{4}{W}|Sorcery|||Search your library for up to three Aura cards with different names, reveal them, and put them into your hand. Then shuffle your library.| Augury Owl|Planechase Anthology|14|C|{1}{U}|Creature - Bird|1|1|Flying$When Augury Owl enters the battlefield, scry 3.| Cancel|Planechase Anthology|15|C|{1}{U}{U}|Instant|||Counter target spell.| @@ -31947,7 +31947,7 @@ Nullmage Advocate|Planechase Anthology|70|C|{2}{G}|Creature - Insect Druid|2|3|{ Ondu Giant|Planechase Anthology|71|C|{3}{G}|Creature - Giant Druid|2|4|When Ondu Giant enters the battlefield, you may search you library for a basic land card, put it onto the battlefield tapped, then shuffle your library.| Overrun|Planechase Anthology|72|U|{2}{G}{G}{G}|Sorcery|||Creatures you control get +3/+3 and gain trample until end of turn.| Penumbra Spider|Planechase Anthology|73|C|{2}{G}{G}|Creature - Spider|2|4|Reach$When Penumbra Spider dies, create a 2/4 black Spider creature token with reach.| -Predatory Urge|Planechase Anthology|74|R|{3}{G}|Enchantment - Aura|||Enchantment - Aura|||Enchant creature$Enchanted creature has "{T}: This creature deals damage equal to its power to target creature. That creature deals damage equal to its power to this creature."| +Predatory Urge|Planechase Anthology|74|R|{3}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature has "{T}: This creature deals damage equal to its power to target creature. That creature deals damage equal to its power to this creature."| Quiet Disrepair|Planechase Anthology|75|C|{1}{G}|Enchantment - Aura|||Enchant artifact or enchantment$At the beginning of your upkeep, choose one — Destroy enchanted permanent; or You gain 2 life.| Rancor|Planechase Anthology|76|C|{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+0 and has trample.$When Rancor is put into a graveyard from the battlefield, return Rancor to its owner's hand.| Silhana Ledgewalker|Planechase Anthology|77|C|{1}{G}|Creature - Elf Rogue|1|1|Hexproof$Silhana Ledgewalker can't be blocked except by creature with flying.| @@ -32298,7 +32298,7 @@ Nomad Outpost|Commander 2017 Edition|265|U||Land|||Nomad Outpost enters the batt Opal Palace|Commander 2017 Edition|266|C||Land|||{T}: Add {C}.${1}, {T}: Add one mana of any color in your commander's color identity. If you spend this mana to cast your commander, it enters the battlefield with a number of additional +1/+1 counters on it equal to the number of times it's been cast from the command zone this game.| Opulent Palace|Commander 2017 Edition|267|U||Land|||Opulant Palace enters the battlefield tapped.${T}: Add {B}, {G}, or {U}.| Orzhov Basilica|Commander 2017 Edition|268|C||Land|||Orzhov Basilica enters the battlefield tapped.$When Orzhov Basilica enters the battlefield, return a land you control to its owner's hand.${T}: Add {W}{B}.| -Orzhov Guildgate|Commander 2017 Edition|269|C||Land - Gate||||Orzhov Guildgate enters the battlefield tapped.${T}: Add {W} or {B}.| +Orzhov Guildgate|Commander 2017 Edition|269|C||Land - Gate|||Orzhov Guildgate enters the battlefield tapped.${T}: Add {W} or {B}.| Rakdos Carnarium|Commander 2017 Edition|270|C||Land|||Rakdos Carnarium enters the battlefield tapped.$When Rakdos Carnarium enters the battlefield, return a land you control to its owner's hand.${T: Add {B}{R}.| Rakdos Guildgate|Commander 2017 Edition|271|C||Land - Gate|||Rakdos Guildgate enters the battlefield tapped.${T}: Add {B} or {R}.| Rogue's Passage|Commander 2017 Edition|272|U||Land|||{T}: Add {C}.${4}, {T}: Target creature can't be blocked this turn.| @@ -32674,7 +32674,7 @@ Adanto Vanguard|Ixalan|1|U|{1}{W}|Creature - Vampire Soldier|1|1|As long as Adan Ashes of the Abhorrent|Ixalan|2|R|{1}{W}|Enchantment|||Players can't cast spells from graveyards or activate abilities from graveyards.$Whenever a creature dies, you gain 1 life.| Axis of Mortality|Ixalan|3|M|{4}{W}{W}|Enchantment|||At the beginning of your upkeep, you may have two target players exchange life totals.| Bellowing Aegisaur|Ixalan|4|U|{5}{W}|Creature - Dinosaur|3|5|Enrage - Whenever Bellowing Aegisaur is dealt damage, put a +1/+1 counter on each other creature you control.| -Bishop of Rebirth|Ixalan|5|R|Creature - Vampire Cleric|3|4|Vigilance$Whenever Bishop of Rebirth attacks, you may return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.| +Bishop of Rebirth|Ixalan|5|R|{3}{W}{W}|Creature - Vampire Cleric|3|4|Vigilance$Whenever Bishop of Rebirth attacks, you may return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.| Bishop's Soldier|Ixalan|6|C|{1}{W}|Creature - Vampire Soldier|2|2|Lifelink| Bright Reprisal|Ixalan|7|U|{4}{W}|Instant|||Destroy target attacking creature.$Draw a card.| Demystify|Ixalan|8|C|{W}|Instant|||Destroy target enchantment.| @@ -32682,7 +32682,7 @@ Duskborne Skymarcher|Ixalan|9|U|{W}|Creature - Vampire Cleric|1|1|Flying${W}, {T Emissary of Sunrise|Ixalan|10|U|{2}{W}|Creature - Human Cleric|2|1|First strike$When Emissary of Sunrise enters the battlefield, it explores. (Reveal the top card of your library. Put that card into your hand if it's a land. Otherwise, put a +1/+1 counter on this creature, then put the card back or put it into your graveyard.)| Encampment Keeper|Ixalan|11|C|{W}|Creature - Hound|1|1|First strike${7}{W}, Sacrifice Encampment Keeper: Creatures you control get +2/+2 until end of turn.| Glorifier of Dusk|Ixalan|12|U|{3}{W}{W}|Creature - Vampire Soldier|4|4|Pay 2 life: Glorifier of Dusk gains flying until end of turn.$Pay 2 life: Glorifier of Dusk gains vigilance until end of turn.| -Goring Ceratops|Ixalan|13|R|Creature - Dinosaur|3|3|Double strike$Whenever Goring Ceratops attacks, other creatures you control gain double strike until end of turn.| +Goring Ceratops|Ixalan|13|R|{5}{W}{W}|Creature - Dinosaur|3|3|Double strike$Whenever Goring Ceratops attacks, other creatures you control gain double strike until end of turn.| Imperial Aerosaur|Ixalan|14|U|{3}{W}|Creature - Dinosaur|3|3|Flying$When Imperial Aerosaur enters the battlefield, another target creature you control gets +1/+1 and gains flying until end of turn.| Imperial Lancer|Ixalan|15|U|{W}|Creature - Human Knight|1|1|Imperial Lancer has double strike as long as you control a Dinosaur.| Inspiring Cleric|Ixalan|16|U|{2}{W}|Creature - Vampire Cleric|3|2|When Inspiring Cleric enters the battlefield, you gain 4 life.| @@ -33397,10 +33397,10 @@ Goblin Tutor|Unglued|45|U|{R}|Instant|||Roll a six-sided die. If you roll a 1, G Jalum Grifter|Unglued|47|R|{3}{R}{R}|Legendary Creature - Devil|3|5| {1}{R}, {T}: Shuffle Jalum Grifter and two lands you control face down. Target opponent chooses one of those cards. Turn the cards face up. If the opponent chose Jalum Grifter, sacrifice it. Otherwise, destroy target permanent.| Krazy Kow|Unglued|48|C|{3}{R}|Creature - Cow|3|3| At the beginning of your upkeep, roll a six-sided die. If you a roll a 1, sacrifice Krazy Kow and it deals 3 damage to each creature and each player.| Ricochet|Unglued|50|U|{R}|Enchantment|||Whenever a player casts a spell that targets a single player, each player rolls a six-sided die. Change the target of that spell to the player with the lowest result. Reroll to break ties, if necessary.| -Spark Fiend|Unglued|51|R|{4}{R}|Creature - Beast, When Spark Fiend enters the battlefield, roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice Spark Fiend. If you rolled 7 or 11, don't roll dice for Spark Fiend during any of your following upkeeps. If you rolled any other total, note that total. At the beginning of your upkeep, roll two six-sided dice. If you rolled 7, sacrifice Spark Fiend. If you roll the noted total, don't roll dice for Spark Fiend during any of your following upkeeps. Otherwise, do nothing.| +Spark Fiend|Unglued|51|R|{4}{R}|Creature - Beast|5|6| When Spark Fiend enters the battlefield, roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice Spark Fiend. If you rolled 7 or 11, don't roll dice for Spark Fiend during any of your following upkeeps. If you rolled any other total, note that total. At the beginning of your upkeep, roll two six-sided dice. If you rolled 7, sacrifice Spark Fiend. If you roll the noted total, don't roll dice for Spark Fiend during any of your following upkeeps. Otherwise, do nothing.| Strategy, Schmategy|Unglued|52|R|{1}{R}|Sorcery|||Roll a six-sided die. Strategy, Schmategy has the indicated effect. 1 - Do nothing. 2 - Destroy all artifacts. 3 - Destroy all lands. 4 - Strategy, Schmategy deals 3 damage to each creature and each player. 5 - Each player discards their hand and draws seven cards. 6 - Repeat this process two more times.| The Ultimate Nightmare of Wizards of the Coast Customer Service|Unglued|53|U|{X}{Y}{Z}{R}{R}|Sorcery|||The Ultimate Nightmare of Wizards of the Coast® Customer Service deals X damage to each of Y target creatures and Z target players.| -Elvish Impersonators|Unglued|56|C|{3}{G}|Creature - Elves, */*, As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.| +Elvish Impersonators|Unglued|56|C|{3}{G}|Creature - Elves|*|*| As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.| Flock of Rabid Sheep|Unglued|57|U|{X}{G}{G}|Sorcery|||Flip X coins. For each flip you win, create a 2/2 green Sheep creature token named Rabid Sheep.| Free-Range Chicken|Unglued|58|C|{3}{G}|Creature - Bird|3|3| {1}{G}: Roll two six-sided dice. If both results are the same, Free-Range Chicken gets +X/+X until end of turn, where X is that result. If the total of those results is equal to any other total you have rolled this turn for Free-Range Chicken, sacrifice it. (For example, if you roll two 3s, Free-Range Chicken gets +3/+3. If you roll a total of 6 for Free-Range Chicken later that turn, sacrifice it.)| Gerrymandering|Unglued|59|U|{2}{G}|Sorcery|||Exile all lands. Give each player a number of those cards chosen at random equal to the number of those cards the player controlled. Each player returns those cards to the battlefield under that player's control.| @@ -51351,32 +51351,343 @@ Temple of Deceit|Wilds of Eldraine Commander|170|R||Land|||Temple of Deceit ente Temple of Plenty|Wilds of Eldraine Commander|171|R||Land|||Temple of Plenty enters the battlefield tapped.$When Temple of Plenty enters the battlefield, scry 1.${T}: Add {G} or {W}.| Temple of the False God|Wilds of Eldraine Commander|172|U||Land|||{T}: Add {C}{C}. Activate only if you control five or more lands.| Vitu-Ghazi, the City-Tree|Wilds of Eldraine Commander|173|U||Land|||{T}: Add {C}.${2}{G}{W}, {T}: Create a 1/1 green Saproling creature token.| -Caesar, Legion's Emperor|Fallout|1|M|{1}{R}{W}{B}|Legendary Creature - Human Soldier|4|4|Whenever you attack, you may sacrifice another creature. When you do, choose two--$* Create two 1/1 red and white Soldier creature tokens with haste that are tapped and attacking.$* You draw a card and you lose 1 life.$* Caesar, Legion's Emperor deals damage equal to the number of creature tokens you control to target opponent.| +Caesar, Legion's Emperor|Fallout|1|M|{1}{R}{W}{B}|Legendary Creature - Human Soldier|4|4|Whenever you attack, you may sacrifice another creature. When you do, choose two --$* Create two 1/1 red and white Soldier creature tokens with haste that are tapped and attacking.$* You draw a card and you lose 1 life.$* Caesar, Legion's Emperor deals damage equal to the number of creature tokens you control to target opponent.| Dogmeat, Ever Loyal|Fallout|2|M|{R}{G}{W}|Legendary Creature - Dog|3|3|When Dogmeat enters the battlefield, mill five cards, then return an Aura or Equipment card from your graveyard to your hand.$Whenever a creature you control that's enchanted or equipped attacks, create a Junk token.| Dr. Madison Li|Fallout|3|M|{U}{R}{W}|Legendary Creature - Human Scientist|2|3|Whenever you cast an artifact spell, you get {E}.${T}, Pay {E}: Target creature gets +1/+0 and gains trample and haste until end of turn.${T}, Pay {E}{E}{E}: Draw a card.${T}, Pay {E}{E}{E}{E}{E}: Return target artifact card from your graveyard to the battlefield tapped.| The Wise Mothman|Fallout|4|M|{1}{B}{G}{U}|Legendary Creature - Insect Mutant|3|3|Flying$Whenever The Wise Mothman enters the battlefield or attacks, each player gets a rad counter.$Whenever one or more nonland cards are milled, put a +1/+1 counter on each of up to X target creatures, where X is the number of nonland cards milled this way.| +Liberty Prime, Recharged|Fallout|5|M|{2}{U}{R}{W}|Legendary Artifact Creature - Robot|8|8|Vigilance, trample, haste$Whenever Liberty Prime, Recharged attacks or blocks, sacrifice it unless you pay {E}{E}.${2}, {T}, Sacrifice an artifact: You get {E}{E} and draw a card.| +The Master, Transcendent|Fallout|6|M|{1}{B}{G}{U}|Legendary Artifact Creature - Mutant|2|4|When The Master, Transcendent enters the battlefield, target player gets two rad counters.${T}: Put target creature card in a graveyard that was milled this turn onto the battlefield under your control. It's a green Mutant with base power and toughness 3/3.| Mr. House, President and CEO|Fallout|7|M|{R}{W}{B}|Legendary Artifact Creature - Human|0|4|Whenever you roll a 4 or higher, create a 3/3 colorless Robot artifact creature token. If you rolled 6 or higher, instead create that token and a Treasure token.${4}, {T}: Roll a six-sided die plus an additional six-sided die for each mana from Treasures spent to activate this ability.| +Preston Garvey, Minuteman|Fallout|8|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier|4|4|At the beginning of combat on your turn, create a green Aura enchantment token named Settlement attached to up to one target land you control. It has enchant land and "Enchanted land has '{T}: Add one mana of any color.'"$Whenever Preston Garvey, Minuteman attacks, untap each enchanted permanent you control.| +Aradesh, the Founder|Fallout|9|R|{2}{W}|Legendary Creature - Human Soldier|1|4|Enlist$Whenever a creature you control attacks, if it enlisted a creature this combat, the creature that attacked gains double strike until end of turn. If that creature's power is 4 or greater, draw a card.| +Automated Assembly Line|Fallout|10|R|{1}{W}|Artifact|||Whenever one or more artifact creatures you control deal combat damage to a player, you get {E}.$Pay {E}{E}{E}: Create a tapped 3/3 colorless Robot artifact creature token.| +Battle of Hoover Dam|Fallout|11|R|{3}{W}|Enchantment|||As Battle of Hoover Dam enters the battlefield, choose NCR or Legion.$* NCR -- At the beginning of your end step, return target creature card with mana value 3 or less from your graveyard to the battlefield with a finality counter on it.$* Legion -- Whenever a creature you control dies, put two +1/+1 counters on target creature you control.| +Brotherhood Outcast|Fallout|12|U|{2}{W}|Creature - Human Soldier|3|2|When Brotherhood Outcast enters the battlefield, choose one --$* Return target Aura or Equipment card with mana value 3 or less from your graveyard to the battlefield.$* Put a shield counter on target creature.| +Brotherhood Scribe|Fallout|13|R|{1}{W}|Creature - Human Artificer|1|3|Metalcraft -- {T}: You get {E}. Activate only if you control three or more artifacts.$Whenever you get one or more {E} during your turn, creatures you control get +1/+1 until end of turn.| +Codsworth, Handy Helper|Fallout|14|R|{2}{W}|Legendary Artifact Creature - Robot|2|3|Commanders you control have ward {2}.${T}: Add {W}{W}. Spend this mana only to cast Aura and/or Equipment spells.${T}: Attach target Aura or Equipment you control to target creature you control. Activate only as a sorcery.| +Commander Sofia Daguerre|Fallout|15|U|{3}{W}|Legendary Creature - Human Pilot|1|3|Flash$Crash Landing -- When Commander Sofia Daguerre enters the battlefield, destroy up to one target legendary permanent. That permanent's controller creates a Junk token.| Gary Clone|Fallout|16|U|{1}{W}|Creature - Human Citizen|1|3|Squad {2}$Whenever Gary Clone attacks, each creature you control named Gary Clone gets +1/+0 until end of turn.| Idolized|Fallout|17|R|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature has "Whenever this creature attacks alone, it gets +X/+X until end of turn, where X is the number of nonland permanents you control."| Overencumbered|Fallout|18|R|{1}{W}|Enchantment - Aura|||Enchant opponent$When Overencumbered enters the battlefield, enchanted opponent creates a Clue token, a Food token, and a Junk token.$At the beginning of combat on enchanted opponent's turn, that player may pay {1} for each artifact they control. If they don't, creatures can't attack this combat.| +Overseer of Vault 76|Fallout|19|R|{2}{W}|Legendary Creature - Human Advisor|3|3|First Contact -- Whenever Overseer of Vault 76 or another creature with power 3 or less enters the battlefield under your control, put a quest counter on Overseer of Vault 76.$At the beginning of combat on your turn, you may remove three quest counters from among permanents you control. When you do, put a +1/+1 counter on each creature you control and they gain vigilance until end of turn.| +Paladin Danse, Steel Maverick|Fallout|20|U|{2}{W}|Legendary Artifact Creature- Synth Knight|3|3|Vigilance, lifelink$Exile Paladin Danse, Steel Maverick: Each creature you control that's an artifact or Human gains indestructible until end of turn.| +Pre-War Formalwear|Fallout|21|R|{2}{W}|Artifact - Equipment|||When Pre-War Formalwear enters the battlefield, return target creature card with mana value 3 or less from your graveyard to the battlefield and attach Pre-War Formalwear to it.$Equipped creature gets +2/+2 and has vigilance.$Equip {3}| +The Prydwen, Steel Flagship|Fallout|22|R|{4}{W}{W}|Legendary Artifact - Vehicle|6|6|Flying$Whenever another nontoken artifact enters the battlefield under your control, create a 2/2 white Human Knight creature token with "This creature gets +2/+2 as long as an artifact entered the battlefield under your control this turn."$Crew 2| +Securitron Squadron|Fallout|23|R|{1}{W}|Artifact Creature - Robot|2|2|Squad {3}$Vigilance$Whenever a creature token enters the battlefield under your control, put a +1/+1 counter on it.| +Sentry Bot|Fallout|24|R|{4}{W}|Artifact Creature - Robot|2|5|Flash$This spell costs {1} less to cast for each creature attacking you.$When Sentry Bot enters the battlefield, you get {E} for each creature attacking you.$At the beginning of combat on your turn, you may pay {E}{E}{E}. If you do, put a +1/+1 counter on each creature you control.| +Sierra, Nuka's Biggest Fan|Fallout|25|R|{3}{W}|Legendary Creature - Human Citizen|3|4|The Nuka-Cola Challenge -- Whenever one or more creatures you control deal combat damage to a player, put a quest counter on Sierra, Nuka's Biggest Fan and create a Food token.$Whenever you sacrifice a Food, target creature you control gets +X/+X until end of turn, where X is the number of quest counters on Sierra.| +Vault 13: Dweller's Journey|Fallout|26|R|{3}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- For each player, exile up to one other target enchantment or creature that player controls until Vault 13 leaves the battlefield.$II -- You gain 2 life and scry 2.$III -- Return two cards exiled with Vault 13 to the battlefield under their owners' control and put the rest on the bottom of their owners' libraries.| +Vault 75: Middle School|Fallout|27|R|{2}{W}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Exile all creatures with power 4 or greater.$II, III -- Put a +1/+1 counter on each creature you control.| Vault 101: Birthday Party|Fallout|28|R|{3}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Create a 1/1 white Human Soldier creature token and a Food token.$II, III -- You may put an Aura or Equipment card from your hand or graveyard onto the battlefield. If an Equipment is put onto the battlefield this way, you may attach it to a creature you control.| +Yes Man, Personal Securitron|Fallout|29|R|{2}{W}|Legendary Artifact Creature - Robot|2|2|{T}: Target opponent gains control of Yes Man, Personal Securitron. When they do, you draw two cards and put a quest counter on Yes Man. Activate only during your turn.$Wild Card -- When Yes Man leaves the battlefield, its owner creates a tapped 1/1 white Soldier creature token for each quest counter on it.| +Curie, Emergent Intelligence|Fallout|30|R|{1}{U}|Legendary Artifact Creature - Robot|1|3|Whenever Curie, Emergent Intelligence deals combat damage to a player, draw cards equal to its base power.${1}{U}, Exile another nontoken artifact creature you control: Curie becomes a copy of the exiled creature, except it has "Whenever this creature deals combat damage to a player, draw cards equal to its base power."| +James, Wandering Dad|Fallout|31|R|{2}{U}|Legendary Creature - Human Scientist|2|4|e: Add {C}{C}. Spend this mana only to activate abilities.| +Follow Him|Fallout|31|R|{X}{U}{U}|Instant - Adventure|2|4|Investigate X times.| +Jason Bright, Glowing Prophet|Fallout|32|R|{2}{U}|Legendary Creature - Zombie Mutant Advisor|2|3|Whenever a Zombie or Mutant you control dies, if its power was different from its base power, draw a card.$Come Fly With Me -- {2}, Sacrifice a creature: Put a +1/+1 counter on target creature you control. It gains flying until end of turn.| +Mirelurk Queen|Fallout|33|R|{4}{U}|Creature - Crab Mutant|4|4|Vigilance$When Mirelurk Queen enters the battlefield, target player gets two rad counters.$Whenever one or more nonland cards are milled, draw a card, then put a +1/+1 counter on Mirelurk Queen. This ability triggers only once each turn.| +Nerd Rage|Fallout|34|U|{2}{U}|Enchantment - Aura|||Enchant creature$When Nerd Rage enters the battlefield, draw two cards.$Enchanted creature has "You have no maximum hand size" and "Whenever this creature attacks, if you have ten or more cards in hand, it gets +10/+10 until end of turn."| +Nick Valentine, Private Eye|Fallout|35|R|{2}{U}|Legendary Artifact Creature - Synth Detective|2|2|Nick Valentine, Private Eye can't be blocked except by artifact creatures.$Whenever Nick Valentine or another artifact creature you control dies, you may investigate.| +Piper Wright, Publick Reporter|Fallout|36|R|{1}{U}|Legendary Creature - Human Detective|1|2|Whenever Piper Wright deals combat damage to a player, investigate that many times.$Whenever you sacrifice a Clue, put a +1/+1 counter on target creature you control.| Radstorm|Fallout|37|R|{3}{U}|Instant|||Storm$Proliferate.| +Robobrain War Mind|Fallout|38|U|{3}{U}|Artifact Creature - Robot|*|5|Robobrain War Mind's power is equal to the number of cards in your hand.$ Robobrain War Mind enters the battlefield, you get an amount of {E} equal to the number of artifact creatures you control.$Whenever Robobrain War Mind attacks, you may pay {E}{E}{E}. If you do, draw a card.| +Struggle for Project Purity|Fallout|39|R|{3}{U}|Enchantment|||As Struggle for Project Purity enters the battlefield, choose Brotherhood or Enclave.$* Brotherhood - At the beginning of your upkeep, each opponent draws a card. You draw a card for each card drawn this way.$* Enclave Whenever a player attacks you with one or more creatures, that player gets twice that many rad counters.| +Synth Infiltrator|Fallout|40|R|{3}{U}{U}|Artifact Creature - Synth|0|0|Improvise$You may have Synth Infiltrator enters the battlefield as a copy of any creature on the battlefield, except it's a Synth artifact creatire in addition to its other types.| +Vexing Radgull|Fallout|41|U|{1}{U}|Creature - Bird Mutant|1|2|Whenever Vexing Radgull deals combat damage to a player, that player gets two rad counters if they don't have any rad counters. Otherwise, proliferate.| +Bloatfly Swarm|Fallout|42|U|{3}{B}|Creature - Insect Mutant|0|0|Flying$Bloatfly Swarm enters the battlefield with five +1/+1 counters on it.$If damage would be dealt to Bloatfly Swarm while it has a +1/+1 counter on it, prevent that damage, remove that many +1/+1 counters from then give each player a rad counter for each +1/+1 counter removed this way.| +Butch DeLoria, Tunnel Snake|Fallout|43|U|{1}{B}|Legendary Creature - Human Rogue|2|2|Menace$Tunnel Snakes Rule! -- Whenever Butch DeLoria, Tunnel Snake attacks, it gets +1/+1 until end of turn for each other Rogue and/or Snake you control.${1}{B}: Put a menace counter on another target creature. It becomes a Rogue in addition to its other types.| Feral Ghoul|Fallout|44|R|{2}{B}|Creature - Zombie Mutant|2|2|Menace$Whenever another creature you control dies, put a +1/+1 counter on Feral Ghoul.$When Feral Ghoul dies, each opponent gets a number of rad counters equal to its power.| +Hancock, Ghoulish Mayor|Fallout|45|R|{2}{B}|Legendary Creature - Zombie Mutant Advisor|2|1|Each other creature you control that's a Zombie or Mutant gets +X/+X, where X is the number of counters on Hancock, Ghoulish Mayor.$Undying| +Infesting Radroach|Fallout|46|U|{2}{B}|Creature - Insect Mutant|2|2|Flying$Infesting Radroach can't block.$Whenever Infesting Radroach deals combat damage to a player, they get that many rad counters.$Whenever an opponent mills a nonland card, if Infesting Radroach is in your graveyard, you may return it to your hand.| +Nuclear Fallout|Fallout|47|R|{X}{B}{B}|Sorcery|||Each creature gets twice -X/-X until end of turn. Each player gets X rad counters.| +Ruthless Radrat|Fallout|48|U|{1}{B}{B}|Creature - Rat Mutant|2|2|Squad -- Exile four cards from your graveyard.$Menace| +Screeching Scorchbeast|Fallout|49|R|{4}{B}{B}|Creature - Bat Mutant|5|5|Flying, menace$Whenever Screeching Scorchbeast attacks, each player gets two rad counters.$Whenever one or more nonland cards are milled, you may create that many 2/2 black Zombie Mutant creature tokens. Do this only once each turn.| V.A.T.S.|Fallout|50|R|{2}{B}{B}|Instant|||Split second$Choose any number of target creatures with equal toughness. Destroy the chosen creatures.| +Vault 12: The Necropolis|Fallout|51|R|{4}{B}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Each player gets three rad counters.$II -- Create X 2/2 black Zombie Mutant creature tokens, where X is the total number of rad counters among players.$III -- Put two +1/+1 counters on each creature you control that's a Zombie or Mutant.| +Wasteland Raider|Fallout|52|R|{2}{B}{B}|Creature - Human Mercenary|4|3|Squad {2}$When Wasteland Raider enters the battlefield, each player sacrifices a creature.| +Acquired Mutation|Fallout|53|U|{2}{R}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+2 and is goaded.$Whenever enchanted creature attacks, defending player gets two rad counters.| +Assaultron Dominator|Fallout|54|R|{1}{R}|Artifact Creature - Robot|2|2|When Assaultron Dominator enters the battlefield, you get {E}{E}.$Whenever an artifact creature you control attacks, you may pay {E}. If you do, put your choice of a +1/+1, first strike, or trample counter on that creature.| +Bottle-Cap Blast|Fallout|55|U|{4}{R}|Instant|||Improvise$Bottle-Cap Blast deals 5 damage to any target. If excess damage was dealt to a permanent this way, create that many apped Treasure tokens.| +Crimson Caravaneer|Fallout|56|U|{2}{R}|Creature - Human Scout|1|2|Double strike, trample$Whenever Crimson Caravaneer deals combat damage to a player, create a Junk token.| +Duchess, Wayward Tavernkeep|Fallout|57|R|{3}{R}|Legendary Creature - Human Citizen|4|3|Hunters for Hire -- Whenever a creature you control deals combat damage to a player, put a quest counter on it.${1}, Remove a quest counter from a permanent you control: Create a Junk token.| +Grim Reaper's Sprint|Fallout|58|R|{4}{R}|Enchantment - Aura|||Morbid -- This spell costs {3} less to cast if a creature died this turn.$Enchant creature$When Grim Reaper's Sprint enters the battlefield, untap each creature you control. If it's your main phase, there is an additional combat phase after this phase.$Enchanted creature gets +2/+2 and has haste.| +Ian the Reckless|Fallout|59|U|{1}{R}|Legendary Creature - Human Warrior|2|1|Whenever Ian the Reckless attacks, if it's modified, you may have it deal damage equal to its power to you and any target.| +Junk Jet|Fallout|60|R|{1}{R}|Artifact - Equipment|||When Junk Jet enters the battlefield, create a Junk token.${3}, Sacrifice another artifact: Double equipped creature's power until end of turn.$Equip {1}| +Megaton's Fate|Fallout|61|R|{5}{R}|Sorcery|||Choose one --$* Disarm -- Destroy target artifact. Create four Treasure tokens.$* Detonate -- Megaton's Fate deals 8 damage to each creature. Each player gets four rad counters.| +The Motherlode, Excavator|Fallout|62|R|{3}{R}{R}|Legendary Artifact Creature - Robot|5|5|When The Motherlode, Excavator enters the battlefield, choose target opponent. You get an amount of {E} equal to the number of nonbasic lands that player controls.$Whenever The Motherlode attacks, you may pay {E}{E}{E}{E}. When you do, destroy target nonbasic land defending player controls, and creatures that player controls without flying can't block this turn.| +Mysterious Stranger|Fallout|63|R|{2}{R}{R}|Creature -- Human Rogue|3|2|Flash$When Mysterious Stranger enters the battlefield, for each graveyard with an instant or sorcery card in it, exile target instant or sorcery card from that graveyard. If two or more cards are exiled this way, choose one of them at random and copy it. You may cast the copy without paying its mana cost.| +Plasma Caster|Fallout|64|R|{1}{R}|Artifact - Equipment|||Equipped creature gets +1/+1.$Whenever equipped creature attacks, you get {E}{E}.$Pay {E}{E}: Choose target creature that's blocking equipped creature. Flip a coin. If you win the flip, exile the chosen creature. Otherwise, Plasma Caster deals 1 damage to it.$Equip {2}| +Powder Ganger|Fallout|65|R|{2}{R}|Creature - Human Rogue|2|2|Squad {2}$When Powder Ganger enters the battlefield, destroy up to one target artifact.| +Rose, Cutthroat Raider|Fallout|66|R|{2}{R}{R}|Legendary Artifact Creature - Robot|3|2|First strike$Raid -- At end of combat on your turn, if you attacked this turn, create a Junk token for each opponent you attacked.$Whenever you sacrifice a Junk, add {R}.| +Synth Eradicator|Fallout|67|R|{2}{R}|Artifact Creature - Synth Soldier|3|3|Haste$Whenever Synth Eradicator attacks, exile the top card of your library. You may get {E}{E}. If you don't, you may play that card this turn.${T}, Pay {E}{E}{E}: Synth Eradicator deals 3 damage to any target.| +Thrill-Kill Disciple|Fallout|68|R|{2}{R}|Creature|3|2|Squad--{1}, Discard a card.$When Thrill-Kill Disciple dies, create a Junk token.| +Vault 21: House Gambit|Fallout|69|R|{1}{R}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Discard a card, then draw a card.$III -- Reveal up to five nonland cards from your hand. For each of those cards that has the same mana value as another card revealed this way, create a Treasure token.| +Veronica, Dissident Scribe|Fallout|70|R|{2}{R}|Legendary Creature - Human Artificer Rogue|3|3|Menace$Whenever Veronica, Dissident Scribe attacks, you may discard a card. If you do, draw a card.$Whenever you discard one or more nonland cards for the first time each turn, create a Junk token.| +Wild Wasteland|Fallout|71|R|{2}{R}|Enchantment|||Skip your draw step.$At the beginning of your upkeep, exile the top two cards of your library. You may play those cards this turn.| +Animal Friend|Fallout|72|R|{1}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature has "Whenever this creature attacks, create a 1/1 green Squirrel creature token. Put a +1/+1 counter on that token for each Aura and Equipment attached to this creature other than Animal Friend."| +Bighorner Rancher|Fallout|73|U|{4}{G}|Creature - Human Ranger|2|5|Vigilance${T}: Add an amount of {G} equal to the greatest power among creatures you control.$Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control.| +Break Down|Fallout|74|U|{2}{G}|Instant|||Destroy target artifact or enchantment. Create a Junk token.| +Cathedral Acolyte|Fallout|75|U|{1}{G}|Creature - Human Cleric|1|2|Each creature you control with a counter on it has ward {1}.${T}: Put a +1/+1 counter on target creature that entered the battlefield this turn.| +Glowing One|Fallout|76|U|{2}{G}|Creature - Zombie Mutant|2|2|Deathtouch$Whenever Glowing One deals combat damage to a player, they get four rad counters.$Whenever a player mills a nonland card, you gain 1 life.| +Gunner Conscript|Fallout|77|U|{1}{G}|Creature - Human Mercenary|2|2|Trample$Gunner Conscript gets +1/+1 for each Aura and Equipment attached to it.$When Gunner Conscript dies, if it was enchanted, create a Junk token.$When Gunner Conscript dies, if it was equipped, create a Junk token.| +Harold and Bob, First Numens|Fallout|78|R|{2}{G}|Legendary Creature - Treefolk Mutant|3|3|Vigilance, reach$When Harold and Bob, First Numens dies, if it was a creature, return it to the battlefield. It's an Aura enchantment with enchant Forest you control and "Enchanted Forest has '{T}: Add three mana of any one color. You get two rad counters."" Harold and Bob loses all other abilities.| +Lily Bowen, Raging Grandma|Fallout|79|R|{3}{G}|Legendary Creature - Mutant Warrior|0|0|Vigilance$Lily Bowen, Raging Grandma enters the battlefield with two +1/+1 counters on it.$At the beginning of your upkeep, double the number of +1/+1 counters on Lily Bowen if its power is 16 or less. Otherwise, remove all but one +1/+1 counter from it, then you gain 1 life for each +1/+1 counter removed this way.| +Lumbering Megasloth|Fallout|80|U|{10}{G}{G}|Creature - Sloth Mutant|8|8|This spell costs {1} less to cast for each counter among players and permanents.$Trample$Lumbering Megasloth enters the battlefield tapped.| +Power Fist|Fallout|81|R|{1}{G}|Artifact - Equipment|||Equipped creature has trample and "Whenever this creature deals combat damage to a player, put that many +1/+1 counters on it."$Equip {2}| +Rampaging Yao Guai|Fallout|82|R|{X}{G}{G}{G}|Creature - Bear Mutant|2|2|Vigilance, trample$Rampaging Yao Guai enters the battlefield with X +1/+1 counters on it. When Rampaging Yao Guai enters the battlefield, destroy any number of target artifacts and/or enchantments with total mana value X or less.| +Strong Back|Fallout|83|R|{2}{G}|Enchantment - Aura|||Enchant creature$Equip abilities you activate that target enchanted creature cost {3} less to activate.$Aura spells you cast that target enchanted creature cost {3} less to cast.$Enchanted creature gets +2/+2 for each Aura and Equipment attached to it.| +Strong, the Brutish Thespian|Fallout|84|R|{4}{G}{G}|Legendary Creature - Mutant Berserker|7|7|Ward {2}$Enrage -- Whenever Strong is dealt damage, you get three rad counters and put three +1/+1 counters on Strong.$You gain life rather than lose life from radiation.| +Super Mutant Scavenger|Fallout|85|U|{4}{G}|Creature - Mutant Warrior|5|5|Trample$When Super Mutant Scavenger enters the battlefield or dies, return up to one target Aura or Equipment card from your graveyard to your hand.| +Tato Farmer|Fallout|86|R|{2}{G}|Creature - Zombie Mutant Peasant|1|4|Landfall -- Whenever a land enters the battlefield under your control, you may get two rad counters.${T}: Put target land card in a graveyard that was milled this turn onto the battlefield under your control tapped.| +Watchful Radstag|Fallout|87|R|{2}{G}|Creature - Elk Mutant|2|2|Evolve$Whenever Watchful Radstag evolves, create a token that's a copy of it.| +Well Rested|Fallout|88|U|{1}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature has "Whenever this creature becomes untapped, put two +1/+1 counters on it, then you gain 2 life and draw a card. This ability triggers only once each turn."| +Agent Frank Horrigan|Fallout|89|R|{5}{B}{G}|Legendary Creature - Mutant Warrior|8|6|Trample$Agent Frank Horrigan has indestructible as long as it attacked this turn.$Whenever Agent Frank Horrigan enters the battlefield or attacks, proliferate twice.| +Almost Perfect|Fallout|90|R|{4}{G}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature has base power and toughness 9/10 and has indestructible.| Alpha Deathclaw|Fallout|91|R|{4}{B}{G}|Creature - Lizard Mutant|6|6|Menace, trample$When Alpha Deathclaw enters the battlefield or becomes monstrous, destroy target permanent.${5}{B}{G}: Monstrosity 4.| +Arcade Gannon|Fallout|92|R|{2}{W}{U}|Legendary Creature - Human Doctor|2|3|{T}: Draw a card, then discard a card. Put a quest counter on Arcade Gannon.$For Auld Lang Syne -- Once during each of your turns, you may cast an artifact or Human spell from your graveyard with mana value less than or equal to the number of quest counters on Arcade Gannon.| +Armory Paladin|Fallout|93|R|{1}{R}{W}|Creature - Human Knight|3|3|Trample$Whenever you cast an Aura or Equipment spell, exile the top card of your library. You may play that card until the end of your next turn.| +Atomize|Fallout|94|R|{2}{B}{G}|Instant|||Destroy target nonland permanent. Proliferate.| +Boomer Scrapper|Fallout|95|R|{1}{B}{R}|Creature - Human Soldier|1|1|Whenever Boomer Scrapper enters the battlefield or attacks, you lose 1 life and create a Junk token.$Whenever a token you control leaves the battlefield, put a +1/+1 counter on Boomer Scrapper.| +Cait, Cage Brawler|Fallout|96|R|{R}{G}|Legendary Creature - Human Warrior|1|1|As long as it's your turn, Cait, Cage Brawler has indestructible.$Whenever Cait attacks, you and defending player each draw a card, then discard a card. Put two +1/+1 counters on Cait if you discarded the card with the highest mana value among those cards or tied for highest.| +Cass, Hand of Vengeance|Fallout|97|R|{2}{R}{W}|Legendary Creature - Human Ranger|4|3|Vigilance$Whenever Cass or another creature you control dies, if it was enchanted or equipped, return any number of Aura cards that were attached to it from your graveyard to the battlefield attached to target creature, then attach any number of Equipment that were attached to it to that creature.| +Colonel Autumn|Fallout|98|R|{1}{W}{B}|Legendary Creature - Human Soldier|2|3|Lifelink$Exploit$Other legendary creatures you control have exploit.$Whenever a creature you control exploits a creature, put a +1/+1 counter on each creature you control.| +Contaminated Drink|Fallout|99|U|{X}{U}{B}|Instant|||Draw X cards, then you get half X rad counters, rounded up.| +Craig Boone, Novac Guard|Fallout|100|U|{1}{R}{W}|Legendary Creature - Human Soldier|3|3|Reach, lifelink$One for My Baby -- Whenever you attack with two or more creatures, put two quest counters on Craig Boone, Novac Guard. When you do, Craig Boone deals damage equal to the number of quest counters on it to up to one target creature unless that creature's controller has Craig Boone deal that much damage to them.| +Desdemona, Freedom's Edge|Fallout|101|R|{2}{R}{W}|Legendary Creature - Human Rogue|3|4|Vigilance$Whenever Desdemona, Freedom's Edge attacks, target creature card in your graveyard that's an artifact or that has mana value 3 or less gains escape until end of turn. The escape cost is equal to its mana cost plus exile two other cards from your graveyard.| +Elder Arthur Maxson|Fallout|102|R|{1}{W}{B}|Legendary Creature - Human Knight|4|2|Creature tokens you control have training.$Blind Betrayal -- Sacrifice another creature: Elder Arthur Maxson gains indestructible until end of turn.| +Elder Owyn Lyons|Fallout|103|U|{2}{W}{U}|Legendary Creature - Human Knight|3|3|Artifacts you control have ward {1}.$When Elder Owyn Lyons enters the battlefield or dies, return target artifact card from your graveyard to your hand.| +Electrosiphon|Fallout|104|R|{U}{U}{R}|Instant|||Instant Counter target spell. You get an amount of {E} equal to its mana value.| +Inventory Management|Fallout|105|R|{R}{W}|Instant|||Split second$For each Aura and Equipment you control, you may attach it to a creature you control.| +Kellogg, Dangerous Mind|Fallout|106|R|{1}{B}{R}|Legendary Creature - Human Mercenary|3|2|First strike, haste$Whenever Kellogg, Dangerous Mind attacks, create a Treasure token.$Sacrifice five Treasures: Gain control of target creature for as long as you control Kellogg. Activate only as a sorcery.| +Legate Lanius, Caesar's Ace|Fallout|107|U|{2}{B}{R}|Legendary Creature - Human Soldier|2|2|Decimate -- When Legate Lanius enters the battlefield, each opponent sacrifices a tenth of the creatures they control, rounded up.$Whenever an opponent sacrifices a creature, put a +1/+1 counter on Legate Lanius.| +MacCready, Lamplight Mayor|Fallout|108|R|{W}{B}|Legendary Creature - Human Advisor|1|3|Whenever a creature you control with power 2 or less attacks, it gains skulk until end of turn.$Whenever a creature with power 4 or greater attacks you, its controller loses 2 life and you gain 2 life.| +Marcus, Mutant Mayor|Fallout|109|R|{3}{G}{U}|Legendary Creature - Mutant Advisor|4|4|Vigilance, trample$Whenever a creature you control deals combat damage to a player, draw a card if that creature has a +1/+1 counter on it. If it doesn't, put a +1/+1 counter on it.| +Moira Brown, Guide Author|Fallout|110|R|{1}{R}{W}|Legendary Creature - Human Citizen|2|3|When Moira Brown, Guide Author enters the battlefield, create a colorless Equipment artifact token named Wasteland Survival Guide with "Equipped creature gets +1/+1 for each quest counter among permanents you control" and equip {1}.$Whenever you attack, put a quest counter on target nonland permanent you control.| +Mutational Advantage|Fallout|111|R|{1}{G}{U}|Instant|||Permanents you control with counters on them gain hexproof and indestructible until end of turn. Prevent all damage that would be dealt to those permanents this turn. Proliferate.| +Nightkin Ambusher|Fallout|112|U|{2}{U}{B}|Creature - Mutant Warrior|4|4|Ward {2}$When Nightkin Ambusher enters the battlefield, target player gets four rad counters.$Nightkin Ambusher can't be blocked as long as defending player has a rad counter.| +The Nipton Lottery|Fallout|113|R|{2}{B}{R}|Sorcery|||Choose a creature at random. You gain control of that creature until end of turn. Untap it. It gains haste until end of turn. Then destroy all other creatures.| +Paladin Elizabeth Taggerdy|Fallout|114|R|{1}{R}{W}|Legendary Creature - Human Knight|3|2|Battalion -- Whenever Paladin Elizabeth Taggerdy and at least two other creatures attack, draw a card, then you may put a creature card with mana value X or less from your hand onto the battlefield tapped and attacking, where X is Paladin Elizabeth Taggerdy's power.| +Raul, Trouble Shooter|Fallout|115|U|{1}{U}{B}|Legendary Creature - Zombie Mutant Rogue|1|4|Once during each of your turns, you may cast a spell from among cards in your graveyard that were milled this turn.${T}: Each player mills a card.| +Red Death, Shipwrecker|Fallout|116|R|{U}{R}|Legendary Creature - Crab Mutant|1|3|Alluring Eyes -- {T}: Goad target creature an opponent controls. That player draws a card. You add {R}.| Rex, Cyber-Hound|Fallout|117|R|{1}{W}{U}|Legendary Artifact Creature - Robot Dog|2|2|Whenever Rex, Cyber-Hound deals combat damage to a player, they mill two cards and you get {E}{E}.$Pay {E}{E}: Choose target creature card in a graveyard. Exile it with a brain counter on it. Activate only as a sorcery.$Rex has all activated abilities of all cards in exile with brain counters on them.| +Sentinel Sarah Lyons|Fallout|118|R|{3}{R}{W}|Legendary Creature - Human Knight|4|4|Haste$As long as an artifact entered the battlefield under your control this turn, creatures you control get +2/+2.$Battalion -- Whenever Sentinel Sarah Lyons and at least two other creatures attack, Sentinel Sarah Lyons deals damage equal the number of artifacts you control to target player.| +Shaun, Father of Synths|Fallout|119|R|{3}{U}{R}|Legendary Creature - Human Scientist|3|4|Whenever you attack, you may create a tapped and attacking token that's a copy of target attacking legendary creature you control other than Shaun, except it's not legendary and it's a Synth artifact creature in addition to its other types. When Shaun leaves the battlefield, exile all Synth tokens you control.| +Three Dog, Galaxy News DJ|Fallout|120|R|{1}{R}{W}|Legendary Creature - Human Bard|1|5|Whenever you attack, you may pay {2} and sacrifice an Aura attached to Three Dog, Galaxy News DJ. When you sacrifice an Aura this way, for each other attacking creature you control, create a token that's a copy of that Aura attached to that creature.| +Vault 11: Voter's Dilemma|Fallout|121|R|{2}{W}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- For each opponent, you create a 1/1 white Human Soldier creature token.$II, III -- Each player secretly votes for up to one creature, then those votes are revealed. If no creature got votes, each player draws a card. Otherwise, destroy each creature with the most votes or tied for most votes.| +Vault 87: Forced Evolution|Fallout|122|R|{3}{G}{U}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Gain control of target non-Mutant creature for as long as you control Vault 87.$II -- Put a +1/+1 counter on target creature you control. It becomes a Mutant in addition to its other types.$III -- Draw cards equal to the greatest power among Mutants you control.| +Vault 112: Sadistic Simulation|Fallout|123|R|{2}{U}{R}|Enchantment - Saga|||(As this Saga enters the battlefield and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Tap up to one target creature and put a stun counter on it. You get {E}{E}.$III -- Pay any amount of {E}. If you paid one or more {E} this way, shuffle your library, then exile that many cards from the top. You may play one of those cards without paying its mana cost.| +White Glove Gourmand|Fallout|124|U|{2}{W}{B}|Creature - Human Noble|2|2|When White Glove Gourmand enters the battlefield, create two 1/1 white Human Soldier creature tokens.$At the beginning of your end step, if another Human died under your control this turn, create a Food token.| +Young Deathclaws|Fallout|125|U|{2}{B}{G}|Creature - Lizard Mutant|4|2|Menace$Each creature card in your graveyard has scavenge. The scavenge cost is equal to its mana cost.| +Agility Bobblehead|Fallout|126|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${3}, {T}: Up to X target creatures you control each gain haste until end of turn and can't be blocked this turn except by creatures with haste, where X is the number of Bobbleheads you control as you activate this ability.| +Behemoth of Vault|Fallout|127|U|{6}|Artifact Creature - Robot|6|6|Trample$When Behemoth of Vault 0 enters the battlefield, you get {E}{E}{E}{E}.$When Behemoth of Vault 0 dies, you may pay an amount of {E} equal to target nonland permanent's mana value. When you do, destroy that permanent.| +Brotherhood Vertibird|Fallout|128|R|{3}|Artifact - Vehicle|*|4|Flying$Brotherhood Vertibird's power is equal to the number of artifacts you control.$Crew 2| +C.A.M.P.|Fallout|129|U|{3}|Artifact - Fortification|||Whenever fortified land is tapped for mana, put a +1/+1 counter on target creature you control. If that creature shares a color with the mana that land produced, create a Junk token.$Fortify {3}| +Charisma Bobblehead|Fallout|130|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${4}, {T}: Create X 1/1 white Soldier creature tokens, where X is the number of Bobbleheads you control. Activate only as a sorcery.| +ED-E, Lonesome Eyebot|Fallout|131|R|{3}|Legendary Artifact Creature - Robot|2|1|Flying$ED-E My Love - Whenever you attack, if the number of attacking creatures is greater than the number of quest counters on ED-E, Lonesome Eyebot, put a quest counter on it.${2}, Sacrifice ED-E: Draw a card, then draw an additional card for each quest counter on ED-E.| +Endurance Bobblehead|Fallout|132|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${3}, {T}: Up to X target creatures you control get +1/+0 and gain indestructible until end of turn, where X is the number of Bobbleheads you control as you activate this ability. Activate only as a sorcery.| +Expert-Level Safe|Fallout|133|U|{2}|Artifact|||When Expert-Level Safe enters the battlefield, exile the top two cards of your library face down.${1}, {T}: You and target opponent each secretly choose 1, 2, or 3. Then those choices are revealed. If they match, sacrifice Expert-Level Safe and put all cards exiled with it into their owners' hands. Otherwise, exile the top card of your library face down.| Intelligence Bobblehead|Fallout|134|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${5}, {T}: Draw X cards, where X is the number of Bobbleheads you control.| +Luck Bobblehead|Fallout|135|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${1}, {T}: Roll X six-sided dice, where X is the number of Bobbleheads you control. Create a tapped Treasure token for each even result. If you rolled 6 exactly seven times, you win the game.| +Mister Gutsy|Fallout|136|R|{2}|Artifact Creature - Robot Soldier|1|1|Whenever you cast an Aura or Equipment spell, put a +1/+1 counter on Mister Gutsy.$When Mister Gutsy dies, create X Junk tokens, where X is the number of +1/+1 counters on it.| Nuka-Cola Vending Machine|Fallout|137|U|{3}|Artifact|||{1}, {T}: Create a Food token.$Whenever you sacrifice a Food, create a tapped Treasure token.| +Nuka-Nuke Launcher|Fallout|138|R|{2}|Artifact - Equipment|||Equipped creature gets +3/+0 and has intimidate.$Whenever equipped creature attacks, until the end of defending player's next turn, that player gets two rad counters whenever they cast a spell.$Equip {3}| +Perception Bobblehead|Fallout|139|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${3}, {T}: Look at the top X cards of your library, where X is the number of Bobbleheads you control. You may cast a spell with mana value 3 or less from among them without paying its mana cost. Put the rest on the bottom of your library in a random order.| +Pip-Boy 3000|Fallout|140|R|{1}|Artifact - Equipment|||Whenever equipped creature attacks, choose one --$* Sort Inventory -- Draw a card, then discard a card.$* Pick a Perk -- Put a +1/+1 counter on that creature.$* Check Map -- Untap up to two target lands.$Equip {2}| +Recon Craft Theta|Fallout|141|R|{4}|Artifact - Vehicle|4|4|Flying When Recon Craft Theta enters the battlefield, create a 0/0 blue Alien creature token. Put a +1/+1 counter on it.$Whenever Recon Craft Theta attacks, proliferate.$Crew 2| +Silver Shroud Costume|Fallout|142|U|{2}|Artifact - Equipment|||Flash$When Silver Shroud Costume enters the battlefield, attach it to target creature you control. That creature gains shroud until end of turn.$Equipped creature can't be blocked.$Equip {3}| +Strength Bobblehead|Fallout|143|U|{3}|Artifact - Bobblehead|||{T}: Add one mana of any color.${3}, {T}: Put X +1/+1 counters on target creature, where X is the number of Bobbleheads you control. Activate only as a sorcery.| +Survivor's Med Kit|Fallout|144|U|{1}|Artifact|||{1}, {T}: Choose one that hasn't been chosen --$* Stimpak -- Draw a card.$* Fancy Lads Snack Cakes -- Create a Food token.$* RadAway -- Target player loses all rad counters. Sacrifice Survivor's Med Kit.| +T-45 Power Armor|Fallout|145|R|{2}|Artifact - Equipment|||When T-45 Power Armor enters the battlefield, you get {E}{E}.$Equipped creature gets +3/+3 and doesn't untap during its controller's untap step.$At the beginning of your upkeep, you may pay {E}. If you do, untap equipped creature, then put your choice of a menace, trample or lifelink counter on it.$Equip {3}| +Desolate Mire|Fallout|146|R||Land|||{1}, {T}: Add {W}{B}.| +Diamond City|Fallout|147|R||Land|||Diamond City enters the battlefield with a shield counter on it.${T}: Add {C}.${T}: Move a shield counter from Diamond City onto target creature. Activate only if two or more creatures entered the battlefield under your control this turn.| +Ferrous Lake|Fallout|148|R||Land|||{1}, {T}: Add {U}{R}.| +HELIOS One|Fallout|149|R||Land|||{T}: Add {C}.${1}, {T}: You get {E}.${3}, {T}, Pay X {E}, Sacrifice HELIOS One: Destroy target nonland permanent with mana value X. Activate only as a sorcery.| +Junktown|Fallout|150|R||Land|||{T}: Add {C}.${4}{R}, {T}, Sacrifice Junktown: Create three Junk tokens.| +Mariposa Military Base|Fallout|151|R||Land|||You may have Mariposa Military Base enter the battlefield tapped. If you do, you get two rad counters.${T}: Add {C}.${5}, {T}: Draw a card. This ability costs {1} less to activate for each rad counter you have.| +Overflowing Basin|Fallout|152|R||Land|||{1}, {T}: Add {G}{U}.| +Sunscorched Divide|Fallout|153|R||Land|||{1}, {T}: Add {R}{W}.| +Viridescent Bog|Fallout|154|R||Land|||{1}, {T}: Add {B}{G}.| +All That Glitters|Fallout|155|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1 for each artifact and/or enchantment you control.| +Austere Command|Fallout|156|R|{4}{W}{W}|Sorcery|||Choose two --$* Destroy all artifacts.$* Destroy all enchantments.$* Destroy all creatures with mana value 3 or less.$* Destroy all creatures with mana value 4 or greater.| +Captain of the Watch|Fallout|157|R|{4}{W}{W}|Creature - Human Soldier|3|3|Vigilance$Other Soldier creatures you control get +1/+1 and have vigilance.$When Captain of the Watch enters the battlefield, create three 1/1 white Soldier creature tokens.| +Crush Contraband|Fallout|158|U|{3}{W}|Instant|||Choose one or both --$* Exile target artifact.$* Exile target enchantment.| +Dispatch|Fallout|159|U|{W}|Instant|||Tap target creature.$Metalcraft -- If you control three or more artifacts, exile that creature.| +Entrapment Maneuver|Fallout|160|R|{3}{W}|Instant|||Target player sacrifices an attacking creature. You create X 1/1 white Soldier creature tokens, where X is that creature's toughness.| +Hour of Reckoning|Fallout|161|R|{4}{W}{W}{W}|Sorcery|||Convoke$Destroy all nontoken creatures.| +Impassioned Orator|Fallout|162|C|{1}{W}|Creature - Human Cleric|2|2|Whenever another creature enters the battlefield under your control, you gain 1 life.| +Intangible Virtue|Fallout|163|U|{1}{W}|Enchantment|||Creature tokens you control get +1/+1 and have vigilance.| +Keeper of the Accord|Fallout|164|R|{3}{W}|Creature - Human Soldier|3|4|At the beginning of each opponent's end step, if that player controls more creatures than you, create a 1/1 white Soldier creature token.$At the beginning of each opponent's end step, if that player controls more lands than you, you may search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle.| +Mantle of the Ancients|Fallout|165|R|{3}{W}{W}|Enchantment - Aura|||Enchant creature you control$When Mantle of the Ancients enters the battlefield, return any number of target Aura and/or Equipment cards from your graveyard to the battlefield attached to enchanted creature.$Enchanted creature gets +1/+1 for each Aura and Equipment attached to it.| +Marshal's Anthem|Fallout|166|R|{2}{W}{W}|Enchantment|||Multikicker {1}{W}$Creatures you control get +1/+1.$When Marshal's Anthem enters the battlefield, return up to X target creature cards from your graveyard to the battlefield, where X is the number of times Marshal's Anthem was kicked.| +Martial Coup|Fallout|167|R|{X}{W}{W}|Sorcery|||Create X 1/1 white Soldier creature tokens. If X is 5 or more, destroy all other creatures.| +Open the Vaults|Fallout|168|R|{4}{W}{W}|Sorcery|||Return all artifact and enchantment cards from all graveyards to the battlefield under their owners' control.| +Path to Exile|Fallout|169|U|{W}|Instant|||Exile target creature. Its controller may search their library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Puresteel Paladin|Fallout|170|R|{W}{W}|Creature - Human Knight|2|2|Whenever an Equipment enters the battlefield under your control, you may draw a card.$Metalcraft -- Equipment you control have equip {0} as long as you control three or more artifacts.| +Secure the Wastes|Fallout|171|R|{X}{W}|Instant|||Create X 1/1 white Warrior creature tokens.| +Single Combat|Fallout|172|R|{3}{W}{W}|Sorcery|||Each player chooses a creature or planeswalker they control, then sacrifices the rest. Players can't cast creature or planeswalker spells until the end of your next turn.| +Swords to Plowshares|Fallout|173|U|{W}|Instant|||Exile target creature. Its controller gains life equal to its power.| +Valorous Stance|Fallout|174|U|{1}{W}|Instant|||Choose one --$* Target creature gains indestructible until end of turn.$* Destroy target creature with toughness 4 or greater.| +Fraying Sanity|Fallout|175|R|{2}{U}|Enchantment - Aura Curse|||Enchant player$At the beginning of each end step, enchanted player mills X cards, where X is the number of cards put into their graveyard from anywhere this turn.| +Glimmer of Genius|Fallout|176|U|{3}{U}|Instant|||Scry 2, then draw two cards. You get {E}{E}.| +Inexorable Tide|Fallout|177|R|{3}{U}{U}|Enchantment|||Whenever you cast a spell, proliferate.| +Mechanized Production|Fallout|178|M|{2}{U}{U}|Enchantment - Aura|||Enchant artifact you control$At the beginning of your upkeep, create a token that's a copy of enchanted artifact. Then if you control eight or more artifacts with the same name as one another, you win the game.| +One with the Machine|Fallout|179|R|{3}{U}|Sorcery|||Draw cards equal to the highest mana value among artifacts you control.| +Thirst for Knowledge|Fallout|180|U|{2}{U}|Instant|||Draw three cards. Then discard two cards unless you discard an artifact card.| +Whirler Rogue|Fallout|181|U|{2}{U}{U}|Creature - Human Rogue Artificer|2|2|When Whirler Rogue enters the battlefield, create two 1/1 colorless Thopter artifact creature tokens with flying.$Tap two untapped artifacts you control: Target creature can't be blocked this turn.| +Bastion of Remembrance|Fallout|182|U|{2}{B}|Enchantment|||When Bastion of Remembrance enters the battlefield, create a 1/1 white Human Soldier creature token.$Whenever a creature you control dies, each opponent loses 1 life and you gain 1 life.| +Black Market|Fallout|183|R|{3}{B}{B}|Enchantment|||Whenever a creature dies, put a charge counter on Black Market.$At the beginning of your precombat main phase, add {B} for each charge counter on Black Market.| +Deadly Dispute|Fallout|184|C|{1}{B}|Instant|||As an additional cost to cast this spell, sacrifice an artifact or creature.$Draw two cards and create a Treasure token.| +Lethal Scheme|Fallout|185|R|{2}{B}{B}|Instant|||Convoke$Destroy target creature or planeswalker. Each creature that convoked Lethal Scheme connives.| +Morbid Opportunist|Fallout|186|U|{2}{B}|Creature - Human Rogue|1|3|Whenever one or more other creatures die, draw a card. This ability triggers only once each turn.| +Pitiless Plunderer|Fallout|187|U|{3}{B}|Creature - Human Pirate|1|4|Whenever another creature you control dies, create a Treasure token.| +Blasphemous Act|Fallout|188|R|{8}{R}|Sorcery|||This spell costs {1} less to cast for each creature on the battlefield.$Blasphemous Act deals 13 damage to each creature.| +Chaos Warp|Fallout|189|R|{2}{R}|Instant|||The owner of target permanent shuffles it into their library, then reveals the top card of their library. If it's a permanent card, they put it onto the battlefield.| +Loyal Apprentice|Fallout|190|U|{1}{R}|Creature - Human Artificer|2|1|Haste$Lieutenant -- At the beginning of combat on your turn, if you control your commander, create a 1/1 colorless Thopter artifact creature token with flying. That token gains haste until end of turn.| +Sticky Fingers|Fallout|191|C|{R}|Enchantment - Aura|||Enchant creature$Enchanted creature has menace and "Whenever this creature deals combat damage to a player, create a Treasure token."$When enchanted creature dies, draw a card.| +Stolen Strategy|Fallout|192|R|{4}{R}|Enchantment|||At the beginning of your upkeep, exile the top card of each opponent's library. Until end of turn, you may cast spells from among those exiled cards, and you may spend mana as though it were mana of any color to cast those spells.| +Unexpected Windfall|Fallout|193|C|{2}{R}{R}|Instant|||As an additional cost to cast this spell, discard a card.$Draw two cards and create two Treasure tokens.| +Abundant Growth|Fallout|194|C|{G}|Enchantment - Aura|||Enchant land$When Abundant Growth enters the battlefield, draw a card.$Enchanted land has "{T}: Add one mana of any color."| +Branching Evolution|Fallout|195|R|{2}{G}|Enchantment|||If one or more +1/+1 counters would be put on a creature you control, twice that many +1/+1 counters are put on that creature instead.| +Cultivate|Fallout|196|U|{2}{G}|Sorcery|||Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.| +Farseek|Fallout|197|C|{1}{G}|Sorcery|||Search your library for a Plains, Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle.| +Fertile Ground|Fallout|198|C|{1}{G}|Enchantment - Aura|||Enchant land$Whenever enchanted land is tapped for mana, its controller adds an additional one mana of any color.| +Guardian Project|Fallout|199|R|{3}{G}|Enchantment|||Whenever a nontoken creature enters the battlefield under your control, if it doesn't have the same name as another creature you control or a creature card in your graveyard, draw a card.| +Hardened Scales|Fallout|200|R|{G}|Enchantment|||If one or more +1/+1 counters would be put on a creature you control, that many plus one +1/+1 counters are put on it instead.| +Harmonize|Fallout|201|U|{2}{G}{G}|Sorcery|||Draw three cards.| +Heroic Intervention|Fallout|202|R|{1}{G}|Instant|||Permanents you control gain hexproof and indestructible until end of turn.| +Inspiring Call|Fallout|203|U|{2}{G}|Instant|||Draw a card for each creature you control with a +1/+1 counter on it. Those creatures gain indestructible until end of turn.| +Rampant Growth|Fallout|204|C|{1}{G}|Sorcery|||Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Rancor|Fallout|205|U|{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+0 and has trample.$When Rancor is put into a graveyard from the battlefield, return Rancor to its owner's hand.| +Squirrel Nest|Fallout|206|U|{1}{G}{G}|Enchantment - Aura|||Enchant land$Enchanted land has "{T}: Create a 1/1 green Squirrel creature token."| +Tireless Tracker|Fallout|207|R|{2}{G}|Creature - Human Scout|3|2|Landfall -- Whenever a land enters the battlefield under your control, investigate.$Whenever you sacrifice a Clue, put a +1/+1 counter on Tireless Tracker.| +Wild Growth|Fallout|208|C|{G}|Enchantment - Aura|||Enchant land$Whenever enchanted land is tapped for mana, its controller adds an additional {G}.| +Anguished Unmaking|Fallout|209|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| +Assemble the Legion|Fallout|210|R|{3}{R}{W}|Enchantment|||At the beginning of your upkeep, put a muster counter on Assemble the Legion. Then create a 1/1 red and white Soldier creature token with haste for each muster counter on Assemble the Legion.| +Behemoth Sledge|Fallout|211|U|{1}{G}{W}|Artifact - Equipment|||Equipped creature gets +2/+2 and has trample and lifelink.$Equip {3}| +Biomass Mutation|Fallout|212|R|{X}{G/U}{G/U}|Instant|||Creatures you control have base power and toughness X/X until end of turn.| +Casualties of War|Fallout|213|R|{2}{B}{B}{G}{G}|Sorcery|||Choose one or more --$* Destroy target artifact.$* Destroy target creature.$* Destroy target enchantment.$* Destroy target land.$* Destroy target planeswalker.| +Corpsejack Menace|Fallout|214|U|{2}{B}{G}|Creature - Fungus|4|4|If one or more +1/+1 counters would be put on a creature you control, twice that many +1/+1 counters are put on it instead.| +Fervent Charge|Fallout|215|R|{1}{R}{W}{B}|Enchantment|||Whenever a creature you control attacks, it gets +2/+2 until end of turn.| +Find // Finality|Fallout|216|R|{B/G}{B/G}|Sorcery|||Return up to two target creature cards from your graveyard to your hand.$Finality${4}{B}{G}$Sorcery$You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn.| +General's Enforcer|Fallout|217|U|{W}{B}|Creature - Human Soldier|2|3|Legendary Humans you control have indestructible.${2}{W}{B}: Exile target card from a graveyard. If it was a creature card, create a 1/1 white Human Soldier creature token.| +Heroic Reinforcements|Fallout|218|U|{2}{R}{W}|Sorcery|||Create two 1/1 white Soldier creature tokens. Until end of turn, creatures you control get +1/+1 and gain haste.| +Putrefy|Fallout|219|U|{1}{B}{G}|Instant|||Destroy target artifact or creature. It can't be regenerated.| +Ruinous Ultimatum|Fallout|220|R|{R}{R}{W}{W}{W}{B}{B}|Sorcery|||Destroy all nonland permanents your opponents control.| +Wake the Past|Fallout|221|R|{5}{R}{W}|Sorcery|||Return all artifact cards from your graveyard to the battlefield. They gain haste until end of turn.| +Wear // Tear|Fallout|222|U|{1}{R}|Instant|||Destroy target artifact.$Fuse$Tear${W}$Instant$Destroy target enchantment.$Fuse| +Winding Constrictor|Fallout|223|U|{B}{G}|Creature - Snake|2|3|If one or more counters would be put on an artifact or creature you control, that many plus one of each of those kinds of counters are put on that permanent instead.$If you would get one or more counters, you get that many plus one of each of those kinds of counters instead.| +Arcane Signet|Fallout|224|U|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Basilisk Collar|Fallout|225|R|{1}|Artifact - Equipment|||Equipped creature has deathtouch and lifelink.$Equip {2}| +Bloodforged Battle-Axe|Fallout|226|R|{1}|Artifact - Equipment|||Equipped creature gets +2/+0.$Whenever equipped creature deals combat damage to a player, create a token that's a copy of Bloodforged Battle-Axe.$Equip {2}| +Brass Knuckles|Fallout|227|U|{4}|Artifact - Equipment|||When you cast this spell, copy it.$Equipped creature has double strike as long as two or more Equipment are attached to it.$Equip {1}| +Champion's Helm|Fallout|228|R|{3}|Artifact - Equipment|||Equipped creature gets +2/+2.$As long as equipped creature is legendary, it has hexproof.$Equip {1}| +Contagion Clasp|Fallout|229|U|{2}|Artifact|||When Contagion Clasp enters the battlefield, put a -1/-1 counter on target creature.${4}, {T}: Proliferate.| +Everflowing Chalice|Fallout|230|U|{0}|Artifact|||Multikicker {2}$Everflowing Chalice enters the battlefield with a charge counter on it for each time it was kicked.${T}: Add {C} for each charge counter on Everflowing Chalice.| +Explorer's Scope|Fallout|231|C|{1}|Artifact - Equipment|||Whenever equipped creature attacks, look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped.$Equip {1}| +Fireshrieker|Fallout|232|U|{3}|Artifact - Equipment|||Equipped creature has double strike.$Equip {2}| +Lightning Greaves|Fallout|233|U|{2}|Artifact - Equipment|||Equipped creature has haste and shroud.$Equip {0}| +Masterwork of Ingenuity|Fallout|234|R|{1}|Artifact - Equipment|||You may have Masterwork of Ingenuity enter the battlefield as a copy of any Equipment on the battlefield.| +Mind Stone|Fallout|235|U|{2}|Artifact|||{T}: Add {C}.${1}, {T}, Sacrifice Mind Stone: Draw a card.| +Mystic Forge|Fallout|236|R|{4}|Artifact|||You may look at the top card of your library any time.$You may cast artifact spells and colorless spells from the top of your library.${T}, Pay 1 life: Exile the top card of your library.| +Panharmonicon|Fallout|237|R|{4}|Artifact|||If an artifact or creature entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Skullclamp|Fallout|238|U|{1}|Artifact - Equipment|||Equipped creature gets +1/-1.$Whenever equipped creature dies, draw two cards.$Equip {1}| +Sol Ring|Fallout|239|U|{1}|Artifact|||{T}: Add {C}{C}.| +Solemn Simulacrum|Fallout|240|R|{4}|Artifact Creature - Golem|2|2|When Solemn Simulacrum enters the battlefield, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.$When Solemn Simulacrum dies, you may draw a card.| +Steel Overseer|Fallout|241|R|{2}|Artifact Creature - Construct|1|1|{T}: Put a +1/+1 counter on each artifact creature you control.| +Swiftfoot Boots|Fallout|242|U|{2}|Artifact - Equipment|||Equipped creature has hexproof and haste.$Equip {1}| +Talisman of Conviction|Fallout|243|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {R} or {W}. Talisman of Conviction deals 1 damage to you.| +Talisman of Creativity|Fallout|244|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {U} or {R}. Talisman of Creativity deals 1 damage to you.| +Talisman of Curiosity|Fallout|245|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {G} or {U}. Talisman of Curiosity deals 1 damage to you.| +Talisman of Dominance|Fallout|246|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {U} or {B}. Talisman of Dominance deals 1 damage to you.| +Talisman of Hierarchy|Fallout|247|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {W} or {B}. Talisman of Hierarchy deals 1 damage to you.| +Talisman of Indulgence|Fallout|248|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {B} or {R}. Talisman of Indulgence deals 1 damage to you.| +Talisman of Progress|Fallout|249|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {W} or {U}. Talisman of Progress deals 1 damage to you.| +Talisman of Resilience|Fallout|250|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {B} or {G}. Talisman of Resilience deals 1 damage to you.| +Thought Vessel|Fallout|251|C|{2}|Artifact|||You have no maximum hand size.${T}: Add {C}.| +Wayfarer's Bauble|Fallout|252|C|{1}|Artifact|||{2}, {T}, Sacrifice Wayfarer's Bauble: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Ash Barrens|Fallout|253|C||Land|||{T}: Add {C}.$Basic landcycling {1}| +Buried Ruin|Fallout|254|U||Land|||{T}: Add {C}.${2}, {T}, Sacrifice Buried Ruin: Return target artifact card from your graveyard to your hand.| +Canopy Vista|Fallout|255|R||Land - Forest Plains|||({T}: Add {G} or {W}.)$Canopy Vista enters the battlefield tapped unless you control two or more basic lands.| +Canyon Slough|Fallout|256|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$Canyon Slough enters the battlefield tapped.$Cycling {2}| +Cinder Glade|Fallout|257|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$Cinder Glade enters the battlefield tapped unless you control two or more basic lands.| +Clifftop Retreat|Fallout|258|R||Land|||Clifftop Retreat enters the battlefield tapped unless you control a Mountain or a Plains.${T}: Add {R} or {W}.| Command Tower|Fallout|259|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Darkwater Catacombs|Fallout|260|R||Land|||{1}, {T}: Add {U}{B}.| +Dragonskull Summit|Fallout|261|R||Land|||Dragonskull Summit enters the battlefield tapped unless you control a Swamp or a Mountain.${T}: Add {B} or {R}.| +Drowned Catacomb|Fallout|262|R||Land|||Drowned Catacomb enters the battlefield tapped unless you control an Island or a Swamp.${T}: Add {U} or {B}.| +Evolving Wilds|Fallout|263|C||Land|||{T}, Sacrifice Evolving Wilds: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Exotic Orchard|Fallout|264|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.| +Fetid Pools|Fallout|265|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$Fetid Pools enters the battlefield tapped.$Cycling {2}| +Glacial Fortress|Fallout|266|R||Land|||Glacial Fortress enters the battlefield tapped unless you control a Plains or an Island.${T}: Add {W} or {U}.| +Hinterland Harbor|Fallout|267|R||Land|||Hinterland Harbor enters the battlefield tapped unless you control a Forest or an Island.${T}: Add {G} or {U}.| +Irrigated Farmland|Fallout|268|R||Land - Plains Island|||({T}: Add {W} or {U}.)$Irrigated Farmland enters the battlefield tapped.$Cycling {2}| +Isolated Chapel|Fallout|269|R||Land|||Isolated Chapel enters the battlefield tapped unless you control a Plains or a Swamp.${T}: Add {W} or {B}.| +Jungle Shrine|Fallout|270|U||Land|||Jungle Shrine enters the battlefield tapped.${T}: Add {R}, {G}, or {W}.| +Memorial to Glory|Fallout|271|U||Land|||Memorial to Glory enters the battlefield tapped.${T}: Add {W}.${3}{W}, {T}, Sacrifice Memorial to Glory: Create two 1/1 white Soldier creature tokens.| +Mortuary Mire|Fallout|272|C||Land|||Mortuary Mire enters the battlefield tapped.$When Mortuary Mire enters the battlefield, you may put target creature card from your graveyard on top of your library.${T}: Add {B}.| +Mossfire Valley|Fallout|273|R||Land|||{1}, {T}: Add {R}{G}.| +Myriad Landscape|Fallout|274|U||Land|||Myriad Landscape enters the battlefield tapped.${T}: Add {C}.${2}, {T}, Sacrifice Myriad Landscape: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle.| +Mystic Monastery|Fallout|275|U||Land|||Mystic Monastery enters the battlefield tapped.${T}: Add {U}, {R}, or {W}.| +Nesting Grounds|Fallout|276|R||Land|||{T}: Add {C}.${1}, {T}: Move a counter from target permanent you control onto another target permanent. Activate only as a sorcery.| +Nomad Outpost|Fallout|277|U||Land|||Nomad Outpost enters the battlefield tapped.${T}: Add {R}, {W}, or {B}.| +Opulent Palace|Fallout|278|U||Land|||Opulent Palace enters the battlefield tapped.${T}: Add {B}, {G}, or {U}.| +Path of Ancestry|Fallout|279|C||Land|||Path of Ancestry enters the battlefield tapped.${T}: Add one mana of any color in your commander's color identity. When that mana is spent to cast a creature spell that shares a creature type with your commander, scry 1.| +Prairie Stream|Fallout|280|R||Land - Plains Island|||({T}: Add {W} or {U}.)$Prairie Stream enters the battlefield tapped unless you control two or more basic lands.| +Razortide Bridge|Fallout|281|C||Artifact Land|||Razortide Bridge enters the battlefield tapped.$Indestructible${T}: Add {W} or {U}.| +Roadside Reliquary|Fallout|282|U||Land|||{T}: Add {C}.${2}, {T}, Sacrifice Roadside Reliquary: Draw a card if you control an artifact. Draw a card if you control an enchantment.| +Rogue's Passage|Fallout|283|U||Land|||{T}: Add {C}.${4}, {T}: Target creature can't be blocked this turn.| +Rootbound Crag|Fallout|284|R||Land|||Rootbound Crag enters the battlefield tapped unless you control a Mountain or a Forest.${T}: Add {R} or {G}.| +Rustvale Bridge|Fallout|285|C||Artifact Land|||Rustvale Bridge enters the battlefield tapped.$Indestructible${T}: Add {R} or {W}.| +Scattered Groves|Fallout|286|R||Land - Forest Plains|||({T}: Add {G} or {W}.)$Scattered Groves enters the battlefield tapped.$Cycling {2}| +Scavenger Grounds|Fallout|287|R||Land - Desert|||{T}: Add {C}.${2}, {T}, Sacrifice a Desert: Exile all graveyards.| +Shadowblood Ridge|Fallout|288|R||Land|||{1}, {T}: Add {B}{R}.| +Sheltered Thicket|Fallout|289|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$Sheltered Thicket enters the battlefield tapped.$Cycling {2}| +Silverbluff Bridge|Fallout|290|C||Artifact Land|||Silverbluff Bridge enters the battlefield tapped.$Indestructible${T}: Add {U} or {R}.| +Skycloud Expanse|Fallout|291|R||Land|||{1}, {T}: Add {W}{U}.| +Smoldering Marsh|Fallout|292|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$Smoldering Marsh enters the battlefield tapped unless you control two or more basic lands.| +Spire of Industry|Fallout|293|R||Land|||{T}: Add {C}.${T}, Pay 1 life: Add one mana of any color. Activate only if you control an artifact.| +Sulfur Falls|Fallout|294|R||Land|||Sulfur Falls enters the battlefield tapped unless you control an Island or a Mountain.${T}: Add {U} or {R}.| +Sungrass Prairie|Fallout|295|R||Land|||{1}, {T}: Add {G}{W}.| +Sunken Hollow|Fallout|296|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$Sunken Hollow enters the battlefield tapped unless you control two or more basic lands.| +Sunpetal Grove|Fallout|297|R||Land|||Sunpetal Grove enters the battlefield tapped unless you control a Forest or a Plains.${T}: Add {G} or {W}.| +Tainted Field|Fallout|298|U||Land|||{T}: Add {C}.${T}: Add {W} or {B}. Activate only if you control a Swamp.| +Tainted Isle|Fallout|299|U||Land|||{T}: Add {C}.${T}: Add {U} or {B}. Activate only if you control a Swamp.| +Tainted Peak|Fallout|300|U||Land|||{T}: Add {C}.${T}: Add {B} or {R}. Activate only if you control a Swamp.| +Tainted Wood|Fallout|301|U||Land|||{T}: Add {C}.${T}: Add {B} or {G}. Activate only if you control a Swamp.| +Temple of Abandon|Fallout|302|R||Land|||Temple of Abandon enters the battlefield tapped.$When Temple of Abandon enters the battlefield, scry 1.${T}: Add {R} or {G}.| +Temple of Deceit|Fallout|303|R||Land|||Temple of Deceit enters the battlefield tapped.$When Temple of Deceit enters the battlefield, scry 1.${T}: Add {U} or {B}.| +Temple of Enlightenment|Fallout|304|R||Land|||Temple of Enlightenment enters the battlefield tapped.$When Temple of Enlightenment enters the battlefield, scry 1.${T}: Add {W} or {U}.| +Temple of Epiphany|Fallout|305|R||Land|||Temple of Epiphany enters the battlefield tapped.$When Temple of Epiphany enters the battlefield, scry 1.${T}: Add {U} or {R}.| +Temple of Malady|Fallout|306|R||Land|||Temple of Malady enters the battlefield tapped.$When Temple of Malady enters the battlefield, scry 1.${T}: Add {B} or {G}.| +Temple of Malice|Fallout|307|R||Land|||Temple of Malice enters the battlefield tapped.$When Temple of Malice enters the battlefield, scry 1.${T}: Add {B} or {R}.| +Temple of Mystery|Fallout|308|R||Land|||Temple of Mystery enters the battlefield tapped.$When Temple of Mystery enters the battlefield, scry 1.${T}: Add {G} or {U}.| +Temple of Plenty|Fallout|309|R||Land|||Temple of Plenty enters the battlefield tapped.$When Temple of Plenty enters the battlefield, scry 1.${T}: Add {G} or {W}.| +Temple of Silence|Fallout|310|R||Land|||Temple of Silence enters the battlefield tapped.$When Temple of Silence enters the battlefield, scry 1.${T}: Add {W} or {B}.| +Temple of the False God|Fallout|311|U||Land|||{T}: Add {C}{C}. Activate only if you control five or more lands.| +Temple of Triumph|Fallout|312|R||Land|||Temple of Triumph enters the battlefield tapped.$When Temple of Triumph enters the battlefield, scry 1.${T}: Add {R} or {W}.| +Terramorphic Expanse|Fallout|313|C||Land|||{T}, Sacrifice Terramorphic Expanse: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Treasure Vault|Fallout|314|R||Artifact Land|||{T}: Add {C}.${X}{X}, {T}, Sacrifice Treasure Vault: Create X Treasure tokens.| +Windbrisk Heights|Fallout|315|R||Land|||Hideaway 4$Windbrisk Heights enters the battlefield tapped.${T}: Add {W}.${W}, {T}: You may play the exiled card without paying its mana cost if you attacked with three or more creatures this turn.| +Woodland Cemetery|Fallout|316|R||Land|||Woodland Cemetery enters the battlefield tapped unless you control a Swamp or a Forest.${T}: Add {B} or {G}.| Plains|Fallout|317|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Fallout|319|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Fallout|321|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Fallout|323|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Fallout|325|C||Basic Land - Forest|||({T}: Add {G}.)| -Arcane Signet|Fallout|356|U|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Hullbreaker Horror|Fallout|344|R|{5}{U}{U}|Creature - Kraken Horror|7|8|Flash$This spell can't be countered.$Whenever you cast a spell, choose up to one --$* Return target spell you don't control to its owner's hand.$* Return target nonland permanent to its owner's hand.| +Lord of the Undead|Fallout|345|R|{1}{B}{B}|Creature - Zombie|2|2|Other Zombie creatures get +1/+1.${1}{B}, {T}: Return target Zombie card from your graveyard to your hand.| +Grave Titan|Fallout|346|M|{4}{B}{B}|Creature - Giant|6|6|Deathtouch$Whenever Grave Titan enters the battlefield or attacks, create two 2/2 black Zombie creature tokens.| +Vigor|Fallout|347|R|{3}{G}{G}{G}|Creature - Elemental Incarnation|6|6|Trample$If damage would be dealt to another creature you control, prevent that damage. Put a +1/+1 counter on that creature for each 1 damage prevented this way.$When Vigor is put into a graveyard from anywhere, shuffle it into its owner's library.| +Ayula, Queen Among Bears|Fallout|348|R|{1}{G}|Legendary Creature - Bear|2|2|Whenever another Bear enters the battlefield under your control, choose one --$* Put two +1/+1 counters on target Bear.$* Target Bear you control fights target creature you don't control.| +Tarmogoyf|Fallout|349|M|{1}{G}|Creature - Lhurgoyf|*|1+*|Tarmogoyf's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1.| +Hornet Queen|Fallout|350|R|{4}{G}{G}{G}|Creature - Insect|2|2|Flying, deathtouch$When Hornet Queen enters the battlefield, create four 1/1 green Insect creature tokens with flying and deathtouch.| +Gemrazer|Fallout|351|R|{3}{G}|Creature - Beast|4|4|Mutate {1}{G}{G}$Reach, trample$Whenever this creature mutates, destroy target artifact or enchantment an opponent controls.| +Walking Ballista|Fallout|352|R|{X}{X}|Artifact Creature - Construct|0|0|Walking Ballista enters the battlefield with X +1/+1 counters on it.${4}: Put a +1/+1 counter on Walking Ballista.$Remove a +1/+1 counter from Walking Ballista: It deals 1 damage to any target.| +Farewell|Fallout|353|R|{4}{W}{W}|Sorcery|||Choose one or more --$* Exile all artifacts.$* Exile all creatures.$* Exile all enchantments.$* Exile all graveyards.| +Ravages of War|Fallout|354|M|{3}{W}|Sorcery|||Destroy all lands.| +Vandalblast|Fallout|355|U|{R}|Sorcery|||Destroy target artifact you don't control.$Overload {4}{R}| Crucible of Worlds|Fallout|357|M|{3}|Artifact|||You may play lands from your graveyard.| -Sol Ring|Fallout|359|M|{1}|Artifact|||{T}: Add {C}{C}.| Wasteland|Fallout|361|R||Land|||{T}: Add {C}.${T}, Sacrifice Wasteland: Destroy target nonbasic land.| +War Room|Fallout|1068|R||Land|||{T}: Add {C}.${3}, {T}, Pay life equal to the number of colors in your commanders' color identity: Draw a card.| Admiral Brass, Unsinkable|Lost Caverns of Ixalan Commander|1|M|{2}{U}{B}{R}|Legendary Creature - Human Pirate|3|3|When Admiral Brass, Unsinkable enters the battlefield, mill four cards.$At the beginning of combat on your turn, you may return target Pirate creature card from your graveyard to the battlefield with a finality counter on it. It has base power and toughness 4/4. It gains haste until end of turn.| Clavileno, First of the Blessed|Lost Caverns of Ixalan Commander|2|M|{1}{W}{B}|Legendary Creature - Vampire Cleric|2|2|Whenever you attack, target attacking Vampire that isn't a Demon becomes a Demon in addition to its other types. It gains "When this creature dies, draw a card and create a tapped 4/3 white and black Vampire Demon creature token with flying."| Hakbal of the Surging Soul|Lost Caverns of Ixalan Commander|3|M|{2}{G}{U}|Legendary Creature - Merfolk Scout|3|3|At the beginning of combat on your turn, each Merfolk creature you control explores.$Whenever Hakbal of the Surging Soul attacks, you may put a land card from your hand onto the battlefield. If you don't, draw a card.| @@ -52081,3 +52392,161 @@ Knowledge Is Power|Murders at Karlov Manor Commander|42|R|{3}{W}{U}|Enchantment| Take the Bait|Murders at Karlov Manor Commander|43|R|{2}{R}{W}|Instant|||Cast this spell only during an opponent's turn and only during combat.$Prevent all combat damage that would be dealt to you and planeswalkers you control this turn. Untap all attacking creatures and goad them. After this phase, there is an additional combat phase.| Panoptic Projektor|Murders at Karlov Manor Commander|44|R|{4}|Artifact|||{T}: The next face-down creature spell you cast this turn costs {3} less to cast.$If turning a face-down permanent face up causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| Ransom Note|Murders at Karlov Manor Commander|45|R|{1}|Artifact - Clue|||When Ransom Note enters the battlefield, surveil 1.${2}, Sacrifice Ransom Note: Choose one --$* Cloak the top card of your library.$* Goad target creature.$* Draw a card.| +Fblthp, Lost on the Range|Outlaws of Thunder Junction|48|R|{1}{U}{U}|Legendary Creature - Homunculus|1|1|Ward {2}$You may look at the top card of your library any time.$The top card of your library has plot. The plot cost is equal to its mana cost.$You may plot nonland cards from the top of your library.| +Tinybones, the Pickpocket|Outlaws of Thunder Junction|109|M|{B}|Legendary Creature - Skeleton Rogue|1|1|Deathtouch$Whenever Tinybones, the Pickpocket deals combat damage to a player, you may cast target nonland permanent card from that player's graveyard, and mana of any type can be spent to cast that spell.| +Hell to Pay|Outlaws of Thunder Junction|126|R|{X}{R}|Sorcery|||Hell to Pay deals X damage to target creature. Create a number of tapped Treasure tokens equal to the amount of excess damage dealt to that creature this way.| +Oko, the Ringleader|Outlaws of Thunder Junction|223|M|{2}{G}{U}|Legendary Planeswalker - Oko|3|At the beginning of combat on your turn, Oko, the Ringleader becomes a copy of up to one target creature you control until end of turn, except he has hexproof.$+1: Draw two cards. If you've committed a crime this turn, discard a card. Otherwise, discard two cards.$-1: Create a 3/3 green Elk creature token.$-5: For each other nonland permanent you control, create a token that's a copy of that permanent.| +Plains|Outlaws of Thunder Junction|272|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Outlaws of Thunder Junction|273|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Outlaws of Thunder Junction|274|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Outlaws of Thunder Junction|275|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Outlaws of Thunder Junction|276|C||Basic Land - Forest|||({T}: Add {G}.)| +Emrakul, the World Anew|Modern Horizons 3|6|M|{12}|Legendary Creature - Eldrazi|12|12|When you cast this spell, gain control of all creatures target player controls.$Flying, protection from spells and from permanents that were cast this turn$When Emrakul, the World Anew leaves the battlefield, sacrifice all creatures you control.$Madness--Pay six {C}.| +It That Heralds the End|Modern Horizons 3|9|U|{1}{C}|Creature - Eldrazi Drone|2|2|Colorless spells you cast with mana value 7 or greater cost {1} less to cast.$Other colorless creatures you control get +1/+1.| +Flare of Cultivation|Modern Horizons 3|154|R|{1}{G}{G}|Sorcery|||You may sacrifice a nontoken green creature rather than pay this spell's mana cost.$Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.| +Psychic Frog|Modern Horizons 3|199|R|{U}{B}|Creature - Frog|1|2|Whenever Psychic Frog deals combat damage to a player or planeswalker, draw a card.$Discard a card: Put a +1/+1 counter on Psychic Frog.$Exile three cards from your graveyard: Psychic Frog gains flying until end of turn. Some frogs poison the body. Some frogs poison the mind.| +Scurry of Gremlins|Modern Horizons 3|203|U|{2}{R}{W}|Enchantment|||When Scurry of Gremlins enters the battlefield, create two 1/1 red Gremlin creature tokens. Then you get an amount of {E} equal to the number of creatures you control.$Pay {E}{E}{E}{E}: Creatures you control get +1/+0 and gain haste until end of turn.| +Bloodstained Mire|Modern Horizons 3|216|R||Land|||{T}, Pay 1 life, Sacrifice Bloodstained Mire: Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle.| +Flooded Strand|Modern Horizons 3|220|R||Land|||{T}, Pay 1 life, Sacrifice Flooded Strand: Search your library for a Plains or Island card, put it onto the battlefield, then shuffle.| +Polluted Delta|Modern Horizons 3|224|R||Land|||{T}, Pay 1 life, Sacrifice Polluted Delta: Search your library for an Island or Swamp card, put it onto the battlefield, then shuffle.| +Windswept Heath|Modern Horizons 3|235|R||Land|||{T}, Pay 1 life, Sacrifice Windswept Heath: Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle.| +Wooded Foothills|Modern Horizons 3|236|R||Land|||{T}, Pay 1 life, Sacrifice Wooded Foothills: Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle.| +Ajani, Nacatl Pariah|Modern Horizons 3|237|M|{1}{W}|Legendary Creature - Cat Warrior|1|2|When Ajani, Nacatl Pariah enters the battlefield, create a 2/1 white Cat Warrior creature token.$Whenever one or more other Cats you control die, you may exile Ajani, then return him to the battlefield transformed under his owner's control.| +Ajani, Nacatl Avenger|Modern Horizons 3|237|M||Legendary Planeswalker - Ajani|3|+2: Put a +1/+1 counter on each Cat you control.$0: Create a 2/1 white Car Warrior creature token. When you do, if you control a red permanent other than Ajani, Nacatl Avenger, he deals damage equal to the number of creatures you control to any target.$-4: Each opponent chooses an artifact, a creature, an enchantment and a planeswalker from among the nonland permanents they control, then sacrifices the rest.| +Laelia, the Blade Reforged|Modern Horizons 3|281|R|{2}{R}|Legendary Creature - Spirit Warrior|2|2|Haste$Whenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn.$Whenever one or more cards are put into exile from your library and/or your graveyard, put a +1/+1 counter on Laelia.| +Priest of Titania|Modern Horizons 3|286|U|{1}{G}|Creature - Elf Druid|1|1|{T}: Add {G} for each Elf on the battlefield.| +Plains|Modern Horizons 3|304|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Modern Horizons 3|305|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Modern Horizons 3|306|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Modern Horizons 3|307|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Modern Horizons 3|308|C||Basic Land - Forest|||({T}: Add {G}.)| +Snow-Covered Wastes|Modern Horizons 3|309|U||Basic Snow Land|||{T}: Add {C}.| +Nexus of Becoming|The Big Score|25|M|{6}|Artifact|||At the beginning of combat on your turn, draw a card. Then you may exile an artifact or creature card from your hand. If you do, create a token that's a copy of the exiled card, except it's a 3/3 Golem artifact creature in addition to its other types.| +Sword of Wealth and Power|The Big Score|26|M|{3}|Artifact- Equipment|||Equipped creature gets +2/+2 and has protection from instants and from sorceries.$Whenever equipped creature deals combat damage to a player, create a Treasure token. When you next cast an instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.$Equip {2}| +Lumra, Bellow of the Woods|Bloomburrow|183|M|{4}{G}{G}|Legendary Creature - Elemental Bear|*|*|Vigilance, reach$Lumra, Bellow of the Woods's power and toughness are each equal to the number of lands you control.$When Lumra enters, mill four cards. Then return all land cards from your graveyard to the battlefield tapped.| +Mabel, Heir to Cragflame|Bloomburrow|224|R|{1}{R}{W}|Legendary Creature - Mouse Soldier|3|3|Other Mice you control get +1/+1.$When Mabel, Heir to Cragflame enters, create Cragflame, a legendary colorless Equipment artifact token with "Equipped creature gets +1/+1 and has vigilance, trample, and haste" and equip {2}.| +Mountain|Bloomburrow|274|C||Basic Land - Mountain|||({T}: Add {R}.)| +Bria, Riptide Rogue|Bloomburrow|379|M|{2}{U}{R}|Legendary Creature - Otter Rogue|3|3|Prowess$Other creatures you control have prowess.$Whenever you cast a noncreature spell, target creature you control can't be blocked this turn.| +Byrke, Long Ear of the Law|Bloomburrow|380|M|{4}{G}{W}|Legendary Creature - Rabbit Soldier|4|4|Vigilance$When Byrke, Long Ear of the Law enters, put a +1/+1 counter on each of up to two target creatures.$Whenever a creature you control with a +1/+1 counter on it attacks, double the number of +1/+1 counters on it.| +Leonardo da Vinci|Assassin's Creed|20|M|{2}{U}|Legendary Creature - Human Artificer|3|3|{3}{U}{U}: Until end of turn, Thopters you control have base power and toughness X/X, where X is the number of cards in your hand.${2}{U}, {T}: Draw a card, then discard a card. If the discarded card was an artifact card, exile it from your graveyard. If you do, create a token that's a copy of it, except it's a 0/2 Thopter artifact creature with flying in addition to its other types.| +Ezio Auditore da Firenze|Assassin's Creed|25|M|{1}{B}|Legendary Creature - Human Assassin|3|2|Menace$Assassin spells you cast have freerunning {B}{B}.$Whenever Ezio deals combat damage to a player, you may pay {W}{U}{B}{R}{G} if that player has 10 or less life. When you do, that player loses the game.| +Altair Ibn-La'Ahad|Assassin's Creed|45|M|{R}{W}{B}|Legendary Creature - Human Assassin|3|3|First strike$Whenever Altair Ibn-La'Ahad attacks, exile up to one target Assassin creature card from your graveyard with a memory counter on it. Then for each creature card you own in exile with a memory counter on it, create tapped and attacking token that's a copy of it. Exile those tokens at end of combat.| +Cleopatra, Exiled Pharaoh|Assassin's Creed|52|M|{2}{B}{G}|Legendary Creature - Human Noble|2|4|Allies -- At the beginning of your end step, put a +1/+1 counter on each of up to two other target legendary creatures.$Betrayal -- Whenever a legendary creature with counters on it dies, draw a card for each counter on it. You lose 2 life.| +The Animus|Assassin's Creed|69|R|{2}|Legendary Artifact|||At the beginning of your end step, exile up to one target legendary creature card from a graveyard with a memory counter on it.${T}: Until your next turn, target legendary creature you control becomes a copy of target creature card in exile with a memory counter on it. Activate only as a sorcery.| +Hidden Blade|Assassin's Creed|73|U|{2}|Artifact - Equipment|||Flash$When Hidden Blade enters the battlefield, attach it to target creature you control. If that creature is an Assassin, it gains deathtouch until end of turn.$Equipped creature gets +1/+0 and has first strike.$Equip {2}| +Temporal Trespass|Assassin's Creed|86|M|{8}{U}{U}{U}|Sorcery|||Delve$Take an extra turn after this one. Exile Temporal Trespass.| +Cover of Darkness|Assassin's Creed|89|R|{1}{B}|Enchantment|||As Cover of Darkness enters the battlefield, choose a creature type.$Creatures of the chosen type have fear.| +Sword of Feast and Famine|Assassin's Creed|99|M|{3}|Artifact - Equipment|||Equipped creature gets +2/+2 and has protection from black and from green.$Whenever equipped creature deals combat damage to a player, that player discards a card and you untap all lands you control.$Equip {2}| +Haystack|Assassin's Creed|175|U|{1}{W}|Artifact|||{2}, {T}: Target creature you control phases out.| +Eivor, Battle-Ready|Assassin's Creed|274|M|{3}{R}{W}|Legendary Creature - Human Assassin Warrior|5|5|Vigilance$Haste$Whenever Eivor, Battle-Ready attacks, it deals damage equal to the number of Equipment you control to each opponent.| +Ezio, Blade of Vengeance|Assassin's Creed|275|M|{3}{U}{B}|Legendary Creature - Human Assassin|5|5|Deathtouch$Whenever an Assassin you control deals combat damage to a player, draw a card.| +Affectionate Indrik|Arena Beginner Set|89|U|{5}{G}|Creature - Beast|4|4|When Affectionate Indrik enters the battlefield, you may have it fight target creature you don't control.| +Air Elemental|Arena Beginner Set|23|U|{3}{U}{U}|Creature - Elemental|4|4|Flying| +Angelic Guardian|Arena Beginner Set|2|R|{4}{W}{W}|Creature - Angel|5|5|Flying$Whenever one or more creatures you control attack, they gain indestructible until end of turn.| +Angelic Reward|Arena Beginner Set|3|U|{3}{W}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +3/+3 and has flying.| +Angel of Vitality|Arena Beginner Set|1|U|{2}{W}|Creature - Angel|2|2|Flying$If you would gain life, you gain that much life plus 1 instead.$Angel of Vitality gets +2/+2 as long as you have 25 or more life.| +Arcane Signet|Arena Beginner Set|117|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Armored Whirl Turtle|Arena Beginner Set|24|C|{2}{U}|Creature - Turtle|0|5|| +Bad Deal|Arena Beginner Set|45|U|{4}{B}{B}|Sorcery|||You draw two cards and each opponent discards two cards. Each player loses 2 life.| +Bombard|Arena Beginner Set|67|C|{2}{R}|Instant|||Bombard deals 4 damage to target creature.| +Bond of Discipline|Arena Beginner Set|4|U|{4}{W}|Sorcery|||Tap all creatures your opponents control. Creatures you control gain lifelink until end of turn.| +Burn Bright|Arena Beginner Set|68|C|{2}{R}|Instant|||Creatures you control get +2/+0 until end of turn.| +Charging Badger|Arena Beginner Set|91|C|{G}|Creature Badger|1|1|Trample| +Charmed Stray|Arena Beginner Set|5|C|{W}|Creature - Cat|1|1|Lifelink$Whenever Charmed Stray enters the battlefield, put a +1/+1 counter on each other creature you control named Charmed Stray.| +Cloudkin Seer|Arena Beginner Set|25|C|{2}{U}|Creature - Elemental Wizard|2|1|Flying$When Cloudkin Seer enters the battlefield, draw a card.| +Colossal Majesty|Arena Beginner Set|92|U|{2}{G}|Enchantment|||At the beginning of your upkeep, if you control a creature with power 4 or greater, draw a card.| +Command Tower|Arena Beginner Set|118|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Confront the Assault|Arena Beginner Set|6|U|{4}{W}|Instant|||Cast this spell only if a creature is attacking you.$Create three 1/1 white Spirit creature tokens with flying.| +Coral Merfolk|Arena Beginner Set|26|C|{1}{U}|Creature - Merfolk|2|1|| +Cruel Cut|Arena Beginner Set|47|C|{1}{B}|Instant|||Destroy target creature with power 2 or less.| +Demon of Loathing|Arena Beginner Set|48|R|{5}{B}{B}|Creature - Demon|7|7|Flying, trample$Whenever Demon of Loathing deals combat damage to a player, that player sacrifices a creature.| +Epic Proportions|Arena Beginner Set|93|R|{4}{G}{G}|Enchantment - Aura|||Flash$Enchant creature$Enchanted creature gets +5/+5 and has trample.| +Eternal Thirst|Arena Beginner Set|49|U|{1}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature has lifelink and "Whenever a creature an opponent controls dies, put a +1/+1 counter on this creature."| +Evolving Wilds|Arena Beginner Set|111|C||Land|||{T}, Sacrifice Evolving Wilds: Search your library for a basic land card and put it onto the battlefield tapped. Then shuffle your library.| +Fearless Halberdier|Arena Beginner Set|69|C|{2}{R}|Creature - Human Warrior|3|2|| +Fencing Ace|Arena Beginner Set|7|C|{1}{W}|Creature - Human Soldier|1|1|Double strike (This creature deals both first-strike and regular combat damage.)| +Feral Roar|Arena Beginner Set|94|C|{1}{G}|Sorcery|||Target creature gets +4/+4 until end of turn.| +Frilled Sea Serpent|Arena Beginner Set|27|C|{4}{U}{U}|Creature - Serpent|4|6|{5}{U}{U}: Frilled Sea Serpent can't be blocked this turn.| +Generous Stray|Arena Beginner Set|95|C|{2}{G}|Creature - Cat|1|2|When Generous Stray enters the battlefield, draw a card.| +Gigantosaurus|Arena Beginner Set|96|R|{G}{G}{G}{G}{G}|Creature - Dinosaur|10|10|| +Glint|Arena Beginner Set|28|C|{1}{U}|Instant|||Target creature you control gets +0/+3 and gains hexproof until end of turn. (It can't be the target of spells or abilities you opponents control.)| +Goblin Gang Leader|Arena Beginner Set|70|U|{2}{R}{R}|Creature - Goblin Warrior|2|2|When Goblin Gang Leader enters the battlefield, create two 1/1 red Goblin creature tokens.| +Goblin Gathering|Arena Beginner Set|71|C|{2}{R}|Sorcery|||Create a number of 1/1 red Goblin creature tokens equal to two plus the number of cards named Goblin Gathering in your graveyard.| +Goblin Trashmaster|Arena Beginner Set|72|R|{2}{R}{R}|Creature - Goblin Warrior|3|3|Other Goblins you control get +1/+1.$Sacrifice a Goblin: Destroy target artifact.| +Goblin Tunneler|Arena Beginner Set|73|C|{1}{R}|Creature - Goblin Rogue|1|1|{T}: Target creature with power 2 or less can't be blocked this turn.| +Goring Ceratops|Arena Beginner Set|8|R|{5}{W}{W}|Creature - Dinosaur|3|3|Double strike$Whenever Goring Ceratops attacks, other creatures you control gain double strike until end of turn.| +Greenwood Sentinel|Arena Beginner Set|97|C|{1}{G}|Creature - Elf Scout|2|2|Vigilance| +Hurloon Minotaur|Arena Beginner Set|74|C|{1}{R}{R}|Creature - Minotaur|2|3|| +Ilysian Caryatid|Arena Beginner Set|98|C|{1}{G}|Creature - Plant|1|1|{T}: Add one mana of any color. If you control a creature with power 4 or greater, add two mana of any one color instead.| +Immortal Phoenix|Arena Beginner Set|75|R|{4}{R}{R}|Creature - Phoenix|5|3|Flying$When Immortal Phoenix dies, return it to its owner's hand.| +Impassioned Orator|Arena Beginner Set|10|C|{1}{W}|Creature - Human Cleric|2|2|Whenever another creature enters the battlefield under your control, you gain 1 life.| +Inescapable Blaze|Arena Beginner Set|76|U|{4}{R}{R}|Instant|||This spell can't be countered.$Inescapable Blaze deals 6 damage to any target.| +Inspiring Commander|Arena Beginner Set|11|R|{4}{W}{W}|Creature - Human Soldier|1|4|Whenever another creature with power 2 or less enters the battlefield under your control, you gain 1 life and draw a card.| +Jungle Delver|Arena Beginner Set|99|C|{G}|Creature - Merfolk Warrior|1|1|{3}{g}: Put a +1/+1 counter on Jungle Delver.| +Knight's Pledge|Arena Beginner Set|12|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+2.| +Krovikan Scoundrel|Arena Beginner Set|50|C|{1}{B}|Creature - Human Rogue|2|1|| +Leonin Warleader|Arena Beginner Set|13|R|{2}{W}{W}|Creature - Cat Soldier|4|4|Whenever Leonin Warleader attacks, create two 1/1 white Cat creature tokens with lifelink that are tapped and attacking.| +Loxodon Line Breaker|Arena Beginner Set|14|C|{2}{W}|Creature - Elephant Soldier|3|2|| +Malakir Cullblade|Arena Beginner Set|51|U|{1}{B}|Creature - Vampire Warrior|1|1|Whenever a creature an opponent controls dies, put a +1/+1 counter on Malakir Cullblade.| +Maniacal Rage|Arena Beginner Set|77|C|{1}{R}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+2 and can't block.| +Molten Ravager|Arena Beginner Set|78|C|{2}{R}|Creature - Elemental|0|4|{R}: Molten Ravager gets +1/+0 until end of turn.| +Moorland Inquisitor|Arena Beginner Set|15|C|{1}{W}|Creature - Human Soldier|2|2|{2}{W}: Moorland Inquisitor gains first strike until end of turn.| +Murder|Arena Beginner Set|53|U|{1}{B}{B}|Instant|||Destroy target creature.| +Nest Robber|Arena Beginner Set|79|C|{1}{R}|Creature - Dinosaur|2|1|Haste| +Nightmare|Arena Beginner Set|54|R|{5}{B}|Creature - Nightmare Horse|*|*|Flying$Nightmare's power and toughness are each equal to the number of Swamps you control.| +Nimble Pilferer|Arena Beginner Set|55|C|{R}|Legendary Creature - Human Rogue|2|1|Flash | +Octoprophet|Arena Beginner Set|29|C|{3}{U}|Creature - Octopus|3|3|When Octoprophet enters the battlefield, scry 2.| +Ogre Battledriver|Arena Beginner Set|80|R|{2}{R}{R}|Creature - Ogre Warrior|3|3|Whenever another creature enters the battlefield under your control, that creature gets +2/+0 and gains haste until end of turn. (It can attack and {tap} this turn.)| +Overflowing Insight|Arena Beginner Set|30|R|{4}{U}{U}{U}|Sorcery|||Target player draws seven cards.| +Pacifism|Arena Beginner Set|16|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block.| +Prized Unicorn|Arena Beginner Set|100|U|{3}{G}|Creature - Unicorn|2|2|All creatures able to block Prized Unicorn do so.| +Rabid Bite|Arena Beginner Set|101|C|{1}{G}|Sorcery|||Target creature you control deals damage equal to its power to target creature you don't control.| +Raging Goblin|Arena Beginner Set|81|C|{R}|Creature - Goblin Berserker|1|1|Haste (This creature can attack and {T} as soon as it comes under your control.)| +Raid Bombardment|Arena Beginner Set|82|U|{2}{R}|Enchantment|||Whenever a creature you control with power 2 or less attacks, Raid Bombardment deals 1 damage to defending player.| +Raise Dead|Arena Beginner Set|56|C|{B}|Sorcery|||Return target creature card from your graveyard to your hand.| +Rampaging Brontodon|Arena Beginner Set|102|R|{5}{G}{G}|Creature - Dinosaur|7|7|Trample$Whenever Rampaging Brontodon attacks, it gets +1/+1 until end of turn for each land you control.| +Reduce to Ashes|Arena Beginner Set|83|C|{4}{R}|Sorcery|||Reduce to Ashes deals 5 damage to target creature. If that creature would die this turn, exile it instead.| +Riddlemaster Sphinx|Arena Beginner Set|31|R|{4}{U}{U}|Creature - Sphinx|5|5|Flying$When Riddlemaster Sphinx enters the battlefield, you may return target creature an opponent controls to its owner's hand.| +Rise from the Grave|Arena Beginner Set|56b|U|{4}{B}|Sorcery|||Put target creature card from a graveyard onto the battlefield under your control. That creature is a black Zombie in addition to its other colors and types.| +River's Favor|Arena Beginner Set|32|C|{1}{R}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1.| +Rumbling Baloth|Arena Beginner Set|103|C|{2}{G}{G}|Creature - Beast|4|4|| +Sanctuary Cat|Arena Beginner Set|17|C|{W}|Creature - Cat|1|2|| +Sanitarium Skeleton|Arena Beginner Set|57|C|{B}|Creature - Skeleton|1|2|{2}{B}: Return Sanitarium Skeleton from your graveyard to your hand.| +Savage Gorger|Arena Beginner Set|58|C|{1}{B}{B}|Creature - Vampire|1|1|Flying$At the beginning of your end step, if an opponent lost life this turn, put a +1/+1 counter on Savage Gorger.| +Scathe Zombies|Arena Beginner Set|59|C|{2}{B}|Creature - Zombie|2|2|| +Sengir Vampire|Arena Beginner Set|60|U|{3}{B}{B}|Creature - Vampire|4|4|Flying$Whenever a creature dealt damage by Sengir Vampire this turn dies, put a +1/+1 counter on Sengir Vampire.| +Sentinel Spider|Arena Beginner Set|104|U|{3}{G}{G}|Creature - Spider|4|4|Vigilance$Reach| +Serra Angel|Arena Beginner Set|18|U|{3}{W}{W}|Creature - Angel|4|4|Flying, vigilance| +Shock|Arena Beginner Set|84|C|{R}|Instant|||Shock deals 2 damage to any target.| +Shorecomber Crab|Arena Beginner Set|32a|C|{U}|Creature - Crab|0|4|| +Shrine Keeper|Arena Beginner Set|19|C|{W}{W}|Creature - Human Cleric|2|2|| +Siege Dragon|Arena Beginner Set|85|R|{5}{R}{R}|Creature - Dragon|5|5|Flying$When Siege Dragon enters the battlefield, destroy all Walls your opponents control.$Whenever Siege Dragon attacks, if defending player controls no Walls, it deals 2 damage to each creature without flying that player controls.| +Skeleton Archer|Arena Beginner Set|61|C|{3}{B}|Creature - Skeleton Archer|3|3|When Skeleton Archer enters the battlefield, it deals 1 damage to any target.| +Sleep|Arena Beginner Set|33|U|{2}{U}{U}|Sorcery|||Tap all creatures target player controls. Those creatures don't untap during that player's next untap step.| +Soulblade Djinn|Arena Beginner Set|34|R|{3}{U}{U}|Creature - Djinn|4|3|Flying$Whenever you cast a noncreature spell, creatures you control get +1/+1 until end of turn.| +Soulhunter Rakshasa|Arena Beginner Set|62|R|{3}{B}{B}|Creature - Demon|5|5|Soulhunter Rakshasa can’t block.$When Soulhunter Rakshasa enters the battlefield, if you cast it from your hand, it deals 1 damage to target opponent for each Swamp you control.| +Soulmender|Arena Beginner Set|20|C|{W}|Creature - Human Cleric|1|1|{T}: You gain 1 life.| +Spiritual Guardian|Arena Beginner Set|21|C|{3}{W}{W}|Creature - Spirit|3|4|When Spiritual Guardian enters the battlefield, you gain 4 life.| +Stony Strength|Arena Beginner Set|105|C|{G}|Instant|||Put a +1/+1 counter on target creature you control. Untap that creature.| +Storm Strike|Arena Beginner Set|86|C|{R}|Instant|||Target creature gets +1/+0 and gains first strike until end of turn. Scry 1.| +Sworn Guardian|Arena Beginner Set|35|C|{1}{U}|Creature - Merfolk Warrior|1|3|| +Tactical Advantage|Arena Beginner Set|22|C|{W}|Instant|||Target blocking or blocked creature you control gets +2/+2 until end of turn.| +Titanic Growth|Arena Beginner Set|106|C|{1}{G}|Instant|||Target creature gets +4/+4 until end of turn.| +Treetop Warden|Arena Beginner Set|107|C|{1}{G}|Creature - Elf Warrior|2|2|| +Typhoid Rats|Arena Beginner Set|63|C|{B}|Creature - Rat|1|1|Deathtouch| +Unlikely Aid|Arena Beginner Set|64|C|{1}{B}|Instant|||Target creature gets +2/+0 and gains indestructible until end of turn.| +Unsummon|Arena Beginner Set|36|C|{U}|Instant|||Return target creature to its owner's hand.| +Vampire Opportunist|Arena Beginner Set|65|C|{1}{B}|Creature - Vampire|2|1|{6}{B}: Each opponent loses 2 life and you gain 2 life.| +Volcanic Dragon|Arena Beginner Set|88|U|{4}{R}{R}|Creature - Dragon|4|4|Flying$Haste| +Wall of Runes|Arena Beginner Set|37|C|{U}|Creature - Wall|0|4|Defender$When Wall of Runes enters the battlefield, scry 1.| +Warden of Evos Isle|Arena Beginner Set|38|U|{2}{U}|Creature - Bird Wizard|2|2|Flying$Creature spells with flying you cast cost {1} less to cast.| +Waterkin Shaman|Arena Beginner Set|39|C|{1}{U}|Creature - Elemental Shaman|2|1|Whenever a creature with flying enters the battlefield under your control, Waterkin Shaman gets +1/+1 until end of turn.| +Waterknot|Arena Beginner Set|40|C|{1}{U}{U}|Enchantment - Aura|||Enchant creature$When Waterknot enters the battlefield, tap enchanted creature.$Enchanted creature doesn't untap during its controller's untap step.| +Wildwood Patrol|Arena Beginner Set|108|C|{2}{G}|Creature - Centaur Scout|4|2|Trample| +Windreader Sphinx|Arena Beginner Set|41|R|{5}{U}{U}|Creature - Sphinx|3|7|Flying$Whenever a creature with flying attacks, you may draw a card.| +Windstorm Drake|Arena Beginner Set|42|U|{4}{U}|Creature - Drake|3|3|Flying$Other creatures you control with flying get +1/+0.| +Winged Words|Arena Beginner Set|43|C|{2}{U}|Sorcery|||This spell costs {1} less to cast if you control a creature with flying.$Draw two cards.| +Witch's Familiar|Arena Beginner Set|66|C|{2}{B}|Creature - Frog|2|3|| +Woodland Mystic|Arena Beginner Set|109|C|{1}{G}|Creature - Elf Druid|1|1|{T}: Add {G}.| +World Shaper|Arena Beginner Set|110|R|{3}{G}|Creature - Merfolk Shaman|3|3|Whenever World Shaper attacks, you may mill three cards.$When World Shaper dies, return all land cards from your graveyard to the battlefield tapped.| +Zephyr Gull|Arena Beginner Set|44|C|{U}|Creature - Bird|1|1|| diff --git a/Utils/mtg-sets-data.txt b/Utils/mtg-sets-data.txt index e42a2ce50e7..2f9c7e8e702 100644 --- a/Utils/mtg-sets-data.txt +++ b/Utils/mtg-sets-data.txt @@ -15,6 +15,7 @@ Aether Revolt|AER| Amonkhet|AKH| Shards of Alara|ALA| Alliances|ALL| +Arena Beginner Set|ANB| Asia Pacific Land Program|APAC| Apocalypse|APC| Alara Reborn|ARB| @@ -23,6 +24,7 @@ Arena League|ARENA| Arabian Nights|ARN| Anthologies|ATH| Antiquities|ATQ| +Assassin's Creed|ACR| Avacyn Restored|AVR| Battle for Zendikar|BFZ| Battlebond|BBD| @@ -30,6 +32,7 @@ Born of the Gods|BNG| Betrayers of Kamigawa|BOK| Battle Royale Box Set|BRB| Beatdown Box Set|BTD| +Bloomburrow|BLB| Commander 2013 Edition|C13| Commander 2014 Edition|C14| Commander 2015 Edition|C15| @@ -176,6 +179,7 @@ Mirage|MIR| Launch Party|MLP| Modern Horizons|MH1| Modern Horizons 2|MH2| +Modern Horizons 3|MH3| Modern Masters 2015|MM2| Modern Masters 2017|MM3| Modern Masters|MMA| @@ -192,6 +196,8 @@ New Phyrexia|NPH| Odyssey|ODY| Oath of the Gatewatch|OGW| Onslaught|ONS| +Outlaws of Thunder Junction|OTJ| +The Big Score|BIG| Magic Origins|ORI| Phyrexia: All Will Be One|ONE| Phyrexia: All Will Be One Commander|ONC| @@ -214,9 +220,11 @@ Rise of the Eldrazi|ROE| Return to Ravnica|RTR| Starter 2000|S00| Starter 1999|S99| +Starter Commander Decks|SCD| Strixhaven: School of Mages|STX| Scourge|SCG| Secret Lair|SLD| +Secret Lair Showdown|SLP| Shadowmoor|SHM| Shadows over Innistrad|SOI| Saviors of Kamigawa|SOK| @@ -275,4 +283,4 @@ Zendikar Rising|ZNR| Zendikar Rising Commander|ZNC| Star Wars|SWS| Happy Holidays|HHO| -Warhammer 40,000|40K| \ No newline at end of file +Warhammer 40,000|40K| diff --git a/readme.md b/readme.md index c51dccbb1ed..5fa5fe973d3 100644 --- a/readme.md +++ b/readme.md @@ -75,6 +75,7 @@ Github issues page contain [popular problems and fixes](https://github.com/magef * [Any: can't run client, could not open ...jvm.cfg](https://github.com/magefree/mage/issues/1272#issuecomment-529789018); * [Any: no texts or small buttons in launcher](https://github.com/magefree/mage/issues/4126); * [Windows: ugly cards, buttons or other GUI drawing artifacts](https://github.com/magefree/mage/issues/4626#issuecomment-374640070); +* [MacOS: can't run on M1/M2](https://github.com/magefree/mage/issues/8406#issuecomment-1011720728); * [MacOS: can't open launcher](https://www.reddit.com/r/XMage/comments/kf8l34/updated_java_on_osx_xmage_not_working/ggej8cq/); * [MacOS: client freezes in GUI (on connect dialog, on new match)](https://github.com/magefree/mage/issues/4920#issuecomment-517944308); * [Linux: run on non-standard OS or hardware like Raspberry Pi](https://github.com/magefree/mage/issues/11611#issuecomment-1879385151); @@ -126,4 +127,4 @@ First steps for Xmage's developers: * [Development Workflow](https://github.com/magefree/mage/wiki/Development-Workflow) * [Development HOWTO Guides](https://github.com/magefree/mage/wiki/Development-HOWTO-Guides) -[Torch icons created by Freepik - Flaticon](https://www.flaticon.com/free-icons/torch) \ No newline at end of file +[Torch icons created by Freepik - Flaticon](https://www.flaticon.com/free-icons/torch)