diff --git a/.gitignore b/.gitignore index 15d874097c..6246ef7be5 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ Mage.Server.Plugins/Mage.Game.FreeForAll/target Mage.Server.Plugins/Mage.Game.MomirDuel/target Mage.Server.Plugins/Mage.Game.MomirGame/target/ Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/target +Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/target/ Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/target/ Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/target Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/target diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index 6e33a0f69b..77780930ce 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-client diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.form b/Mage.Client/src/main/java/mage/client/MageFrame.form index 1c0968259a..40d70fa386 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.form +++ b/Mage.Client/src/main/java/mage/client/MageFrame.form @@ -16,6 +16,14 @@ + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index f4a58e9b4e..d27f510ea8 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -20,6 +20,7 @@ import mage.client.draft.DraftPane; import mage.client.draft.DraftPanel; import mage.client.game.GamePane; import mage.client.game.GamePanel; +import mage.client.game.PlayAreaPanel; import mage.client.plugins.adapters.MageActionCallback; import mage.client.plugins.impl.Plugins; import mage.client.preference.MagePreferences; @@ -72,7 +73,7 @@ import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class MageFrame extends javax.swing.JFrame implements MageClient { @@ -246,8 +247,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { SessionHandler.startSession(this); callbackClient = new CallbackClientImpl(this); connectDialog = new ConnectDialog(); - try - { + try { whatsNewDialog = new WhatsNewDialog(); } catch (NoClassDefFoundError e) { // JavaFX is not supported on old MacOS with OpenJDK @@ -320,10 +320,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { setConnectButtonText(NOT_CONNECTED_TEXT); SwingUtilities.invokeLater(() -> { disableButtons(); - if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_CHECK, "false").equals("true")) { - checkForNewImages(); - } - updateMemUsageTask.execute(); LOGGER.info("Client start up time: " + ((System.currentTimeMillis() - startTime) / 1000 + " seconds")); if (autoConnect()) { @@ -332,7 +328,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { connectDialog.showDialog(); } setWindowTitle(); - }); if (SystemUtil.isMacOSX()) { @@ -354,61 +349,76 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { + ((SessionHandler.getSession() != null && SessionHandler.isConnected()) ? SessionHandler.getVersionInfo() : NOT_CONNECTED_TEXT)); } + private void updateTooltipContainerSizes() { + JPanel cardPreviewContainer; + BigCard bigCard; + JPanel cardPreviewContainerRotated; + BigCard bigCardRotated; + try { + cardPreviewContainer = (JPanel) UI.getComponent(MageComponents.CARD_PREVIEW_CONTAINER); + bigCard = (BigCard) UI.getComponent(MageComponents.CARD_PREVIEW_PANE); + cardPreviewContainerRotated = (JPanel) UI.getComponent(MageComponents.CARD_PREVIEW_CONTAINER_ROTATED); + bigCardRotated = (BigCard) UI.getComponent(MageComponents.CARD_PREVIEW_PANE_ROTATED); + } catch (InterruptedException e) { + LOGGER.fatal("Can't update tooltip panel sizes"); + Thread.currentThread().interrupt(); + return; + } + + int height = GUISizeHelper.enlargedImageHeight; + int width = (int) ((float) height * (float) 0.64); + bigCard.setSize(width, height); + cardPreviewContainer.setBounds(0, 0, width + 80, height + 30); + bigCardRotated.setSize(height, width + 30); + cardPreviewContainerRotated.setBounds(0, 0, height + 80, width + 100 + 30); + } + private void addTooltipContainer() { - final JEditorPane cardInfoPane = (JEditorPane) Plugins.instance.getCardInfoPane(); + JEditorPane cardInfoPane = (JEditorPane) Plugins.instance.getCardInfoPane(); if (cardInfoPane == null) { + LOGGER.fatal("Can't find card tooltip plugin"); return; } cardInfoPane.setLocation(40, 40); cardInfoPane.setBackground(new Color(0, 0, 0, 0)); + UI.addComponent(MageComponents.CARD_INFO_PANE, cardInfoPane); MageRoundPane popupContainer = new MageRoundPane(); popupContainer.setLayout(null); - popupContainer.add(cardInfoPane); popupContainer.setVisible(false); - desktopPane.add(popupContainer, JLayeredPane.POPUP_LAYER); - - UI.addComponent(MageComponents.CARD_INFO_PANE, cardInfoPane); UI.addComponent(MageComponents.POPUP_CONTAINER, popupContainer); - // preview panel normal + + JPanel cardPreviewContainer = new JPanel(); cardPreviewContainer.setOpaque(false); cardPreviewContainer.setLayout(null); - BigCard bigCard = new BigCard(); - int height = GUISizeHelper.enlargedImageHeight; - int width = (int) ((float) height * (float) 0.64); - bigCard.setSize(width, height); - bigCard.setLocation(40, 40); - bigCard.setBackground(new Color(0, 0, 0, 0)); - - cardPreviewContainer.add(bigCard); cardPreviewContainer.setVisible(false); - cardPreviewContainer.setBounds(0, 0, width + 80, height + 30); - - UI.addComponent(MageComponents.CARD_PREVIEW_PANE, bigCard); + desktopPane.add(cardPreviewContainer, JLayeredPane.POPUP_LAYER); UI.addComponent(MageComponents.CARD_PREVIEW_CONTAINER, cardPreviewContainer); - desktopPane.add(cardPreviewContainer, JLayeredPane.POPUP_LAYER); + BigCard bigCard = new BigCard(); + bigCard.setLocation(40, 40); + bigCard.setBackground(new Color(0, 0, 0, 0)); + cardPreviewContainer.add(bigCard); + UI.addComponent(MageComponents.CARD_PREVIEW_PANE, bigCard); - // preview panel rotated JPanel cardPreviewContainerRotated = new JPanel(); cardPreviewContainerRotated.setOpaque(false); cardPreviewContainerRotated.setLayout(null); - bigCard = new BigCard(true); - bigCard.setSize(height, width + 30); - bigCard.setLocation(40, 40); - bigCard.setBackground(new Color(0, 0, 0, 0)); - cardPreviewContainerRotated.add(bigCard); cardPreviewContainerRotated.setVisible(false); - cardPreviewContainerRotated.setBounds(0, 0, height + 80, width + 100 + 30); - - UI.addComponent(MageComponents.CARD_PREVIEW_PANE_ROTATED, bigCard); + desktopPane.add(cardPreviewContainerRotated, JLayeredPane.POPUP_LAYER); UI.addComponent(MageComponents.CARD_PREVIEW_CONTAINER_ROTATED, cardPreviewContainerRotated); - desktopPane.add(cardPreviewContainerRotated, JLayeredPane.POPUP_LAYER); + BigCard bigCardRotated = new BigCard(true); + bigCardRotated.setLocation(40, 40); + bigCardRotated.setBackground(new Color(0, 0, 0, 0)); + cardPreviewContainerRotated.add(bigCardRotated); + UI.addComponent(MageComponents.CARD_PREVIEW_PANE_ROTATED, bigCardRotated); + + updateTooltipContainerSizes(); } private void setGUISizeTooltipContainer() { @@ -563,10 +573,6 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { menu.show(component, 0, component.getHeight()); } - private void checkForNewImages() { - // Removed TODO: Remove related pref code - } - public static void setActive(MagePane frame) { // Always hide not hidden popup window or enlarged card view if a frame is set to active try { @@ -580,8 +586,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { container.repaint(); } } catch (InterruptedException e) { - + LOGGER.fatal("MageFrame error", e); + Thread.currentThread().interrupt(); } + // Nothing to do if (activeFrame == frame) { return; @@ -758,10 +766,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private boolean performConnect(boolean reconnect) { if (currentConnection == null || !reconnect) { - String server = MagePreferences.getServerAddress(); - int port = MagePreferences.getServerPort(); - String userName = MagePreferences.getUserName(server); - String password = MagePreferences.getPassword(server); + String server = MagePreferences.getLastServerAddress(); + int port = MagePreferences.getLastServerPort(); + String userName = MagePreferences.getLastServerUser(); + String password = MagePreferences.getLastServerPassword(); String proxyServer = PREFS.get("proxyAddress", ""); int proxyPort = Integer.parseInt(PREFS.get("proxyPort", "0")); ProxyType proxyType = ProxyType.valueByText(PREFS.get("proxyType", "None")); @@ -820,6 +828,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { popupDebug = new javax.swing.JPopupMenu(); menuDebugTestModalDialog = new javax.swing.JMenuItem(); + menuDebugTestCardRenderModesDialog = new javax.swing.JMenuItem(); desktopPane = new MageJDesktop(); mageToolbar = new javax.swing.JToolBar(); btnPreferences = new javax.swing.JButton(); @@ -850,6 +859,14 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { }); popupDebug.add(menuDebugTestModalDialog); + menuDebugTestCardRenderModesDialog.setText("Test Card Render Modes"); + menuDebugTestCardRenderModesDialog.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + menuDebugTestCardRenderModesDialogActionPerformed(evt); + } + }); + popupDebug.add(menuDebugTestCardRenderModesDialog); + setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); setMinimumSize(new java.awt.Dimension(1024, 768)); @@ -1072,6 +1089,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { popupDebug.show(evt.getComponent(), 0, evt.getComponent().getHeight()); }//GEN-LAST:event_btnDebugMouseClicked + private void menuDebugTestCardRenderModesDialogActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuDebugTestCardRenderModesDialogActionPerformed + final TestCardRenderDialog dialog = new TestCardRenderDialog(); + dialog.showDialog(); + }//GEN-LAST:event_menuDebugTestCardRenderModesDialogActionPerformed + public void downloadImages() { DownloadPicturesService.startDownload(); } @@ -1321,6 +1343,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private javax.swing.JToolBar.Separator jSeparatorImages; private javax.swing.JToolBar.Separator jSeparatorSymbols; private javax.swing.JToolBar mageToolbar; + private javax.swing.JMenuItem menuDebugTestCardRenderModesDialog; private javax.swing.JMenuItem menuDebugTestModalDialog; private javax.swing.JPopupMenu popupDebug; private javax.swing.JToolBar.Separator separatorDebug; @@ -1365,6 +1388,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { return GAMES.get(gameId); } + public static Map getGamePlayers(UUID gameId) { + GamePanel p = GAMES.get(gameId); + return p != null ? p.getPlayers() : new HashMap<>(); + } + public static void removeGame(UUID gameId) { GAMES.remove(gameId); } @@ -1403,23 +1431,25 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } @Override - public void disconnected(final boolean errorCall) { + public void disconnected(final boolean askToReconnect) { if (SwingUtilities.isEventDispatchThread()) { // Returns true if the current thread is an AWT event dispatching thread. - LOGGER.info("DISCONNECTED (Event Dispatch Thread)"); + // REMOTE task, e.g. connecting + LOGGER.info("Disconnected from remote task"); setConnectButtonText(NOT_CONNECTED_TEXT); disableButtons(); hideGames(); hideTables(); } else { - LOGGER.info("DISCONNECTED (NO Event Dispatch Thread)"); + // USER mode, e.g. user plays and got disconnect + LOGGER.info("Disconnected from user mode"); SwingUtilities.invokeLater(() -> { + SessionHandler.disconnect(false); // user already disconnected, can't do any online actions like quite chat setConnectButtonText(NOT_CONNECTED_TEXT); disableButtons(); hideGames(); hideTables(); - SessionHandler.disconnect(false); - if (errorCall) { - UserRequestMessage message = new UserRequestMessage("Connection lost", "The connection to server was lost. Reconnect?"); + if (askToReconnect) { + UserRequestMessage message = new UserRequestMessage("Connection lost", "The connection to server was lost. Reconnect to " + MagePreferences.getLastServerAddress() + "?"); message.setButton1("No", null); message.setButton2("Yes", PlayerAction.CLIENT_RECONNECT); showUserRequestDialog(message); @@ -1583,7 +1613,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } balloonTip.setFont(GUISizeHelper.balloonTooltipFont); - addTooltipContainer(); + updateTooltipContainerSizes(); } public void showWhatsNewDialog(boolean forceToShowPage) { diff --git a/Mage.Client/src/main/java/mage/client/cards/Card.java b/Mage.Client/src/main/java/mage/client/cards/Card.java index 9cd5b3bc87..cde4acf2de 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Card.java +++ b/Mage.Client/src/main/java/mage/client/cards/Card.java @@ -1,10 +1,3 @@ - - - /* - * Card.java - * - * Created on 17-Dec-2009, 9:20:50 PM - */ package mage.client.cards; import mage.cards.CardDimensions; @@ -37,7 +30,6 @@ import java.util.UUID; import static mage.client.constants.Constants.*; /** - * * @author BetaSteward_at_googlemail.com */ @SuppressWarnings("serial") @@ -132,7 +124,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis gSmall.drawImage(ImageHelper.scaleImage(image, Config.dimensions.getFrameWidth(), Config.dimensions.getFrameHeight()), 0, 0, this); gImage.setFont(new Font("Arial", Font.PLAIN, NAME_FONT_MAX_SIZE)); - gImage.drawString(card.getName()+"TEST", CONTENT_MAX_XOFFSET, NAME_MAX_YOFFSET); + gImage.drawString(card.getName() + "TEST", CONTENT_MAX_XOFFSET, NAME_MAX_YOFFSET); if (card.isCreature()) { gImage.drawString(card.getPower() + '/' + card.getToughness(), POWBOX_TEXT_MAX_LEFT, POWBOX_TEXT_MAX_TOP); } else if (card.isPlanesWalker()) { @@ -146,7 +138,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis gImage.dispose(); gSmall.setFont(new Font("Arial", Font.PLAIN, Config.dimensions.getNameFontSize())); - gSmall.drawString(card.getName()+"TEST2", Config.dimensions.getContentXOffset(), Config.dimensions.getNameYOffset()); + gSmall.drawString(card.getName() + "TEST2", Config.dimensions.getContentXOffset(), Config.dimensions.getNameYOffset()); if (card.isCreature()) { gSmall.drawString(card.getPower() + "/-/" + card.getToughness(), Config.dimensions.getPowBoxTextLeft(), Config.dimensions.getPowBoxTextTop()); } else if (card.isPlanesWalker()) { @@ -259,12 +251,12 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis return sbType.toString(); } - + protected void drawDetailed(Graphics2D g) { // Get the size of the card int width = getWidth(); int height = getHeight(); - + g.setColor(Color.black); g.drawRoundRect(0, 0, width, height, 4, 4); g.setColor(Color.white); @@ -309,7 +301,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis @Override public void paintComponent(Graphics graphics) { - drawDetailed((Graphics2D)graphics); + drawDetailed((Graphics2D) graphics); /* Graphics2D g2 = (Graphics2D) graphics; g2.drawImage(small, 0, 0, this); @@ -367,13 +359,13 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis List targets = card.getTargets(); if (targets != null) { for (UUID uuid : targets) { - PlayAreaPanel playAreaPanel = MageFrame.getGame(gameId).getPlayers().get(uuid); + PlayAreaPanel playAreaPanel = MageFrame.getGamePlayers(gameId).get(uuid); if (playAreaPanel != null) { Point target = playAreaPanel.getLocationOnScreen(); Point me = this.getLocationOnScreen(); ArrowBuilder.getBuilder().addArrow(gameId, (int) me.getX() + 35, (int) me.getY(), (int) target.getX() + 40, (int) target.getY() - 40, Color.red, ArrowBuilder.Type.TARGET); } else { - for (PlayAreaPanel pa : MageFrame.getGame(gameId).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(gameId).values()) { MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(uuid); if (permanent != null) { Point target = permanent.getLocationOnScreen(); diff --git a/Mage.Client/src/main/java/mage/client/cards/CardArea.java b/Mage.Client/src/main/java/mage/client/cards/CardArea.java index c98be7e7b0..dfa3650260 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardArea.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardArea.java @@ -1,6 +1,7 @@ package mage.client.cards; import mage.cards.MageCard; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.ClientEventType; import mage.client.util.Event; @@ -30,6 +31,9 @@ public class CardArea extends JPanel implements MouseListener { private Dimension cardDimension; private int verticalCardOffset; + private int customRenderMode = -1; // custom render mode tests + private Dimension customCardSize = null; // custom size for tests + /** * Create the panel. */ @@ -62,7 +66,11 @@ public class CardArea extends JPanel implements MouseListener { } private void setGUISize() { - setCardDimension(GUISizeHelper.otherZonesCardDimension, GUISizeHelper.otherZonesCardVerticalOffset); + if (customCardSize != null) { + setCardDimension(customCardSize, GUISizeHelper.otherZonesCardVerticalOffset); + } else { + setCardDimension(GUISizeHelper.otherZonesCardDimension, GUISizeHelper.otherZonesCardVerticalOffset); + } } public void setCardDimension(Dimension dimension, int verticalCardOffset) { @@ -129,7 +137,8 @@ public class CardArea extends JPanel implements MouseListener { tmp.setAbility(card); // cross-reference, required for ability picker card = tmp; } - MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true); + MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true, + customRenderMode != -1 ? customRenderMode : PreferencesDialog.getRenderMode()); cardPanel.setBounds(rectangle); cardPanel.addMouseListener(this); @@ -265,6 +274,14 @@ public class CardArea extends JPanel implements MouseListener { } } + public void setCustomRenderMode(int customRenderMode) { + this.customRenderMode = customRenderMode; + } + + public void setCustomCardSize(Dimension customCardSize) { + this.customCardSize = customCardSize; + } + @Override public void mouseEntered(MouseEvent e) { } diff --git a/Mage.Client/src/main/java/mage/client/cards/CardDraggerGlassPane.java b/Mage.Client/src/main/java/mage/client/cards/CardDraggerGlassPane.java index e79ea3944c..73c4a1dc09 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardDraggerGlassPane.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardDraggerGlassPane.java @@ -2,6 +2,7 @@ package mage.client.cards; import mage.cards.MageCard; import mage.client.MagePane; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.view.CardView; @@ -45,7 +46,7 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener currentRoot = SwingUtilities.getRootPane(c); // Pane - glassPane = (JComponent)currentRoot.getGlassPane(); + glassPane = (JComponent) currentRoot.getGlassPane(); glassPane.setLayout(null); glassPane.setOpaque(false); glassPane.setVisible(true); @@ -58,7 +59,7 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener if (rootMagePane == null) { throw new RuntimeException("CardDraggerGlassPane::beginDrag not in a MagePane?"); } else { - currentEventRootMagePane = (MagePane)rootMagePane; + currentEventRootMagePane = (MagePane) rootMagePane; } // Hook up events @@ -72,8 +73,8 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener currentCards = new ArrayList<>(source.dragCardList()); // Make a view for the first one and add it to us - dragView = Plugins.instance.getMageCard(currentCards.get(0), null, new Dimension(100, 140), null, true, false); - for (MouseListener l: dragView.getMouseListeners()) { + dragView = Plugins.instance.getMageCard(currentCards.get(0), null, new Dimension(100, 140), null, true, false, PreferencesDialog.getRenderMode()); + for (MouseListener l : dragView.getMouseListeners()) { dragView.removeMouseListener(l); } for (MouseMotionListener l : dragView.getMouseMotionListeners()) { @@ -95,7 +96,7 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener Component mouseOver = SwingUtilities.getDeepestComponentAt(currentEventRootMagePane, e.getX(), e.getY()); while (mouseOver != null) { if (mouseOver instanceof DragCardTarget) { - DragCardTarget target = (DragCardTarget)mouseOver; + DragCardTarget target = (DragCardTarget) mouseOver; MouseEvent targetEvent = SwingUtilities.convertMouseEvent(currentEventRootMagePane, e, mouseOver); if (target != currentDragTarget) { if (currentDragTarget != null) { @@ -116,7 +117,7 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener mouseOver = mouseOver.getParent(); } if (currentDragTarget != null) { - MouseEvent oldTargetEvent = SwingUtilities.convertMouseEvent(currentEventRootMagePane, e, (Component)currentDragTarget); + MouseEvent oldTargetEvent = SwingUtilities.convertMouseEvent(currentEventRootMagePane, e, (Component) currentDragTarget); currentDragTarget.dragCardExit(oldTargetEvent); } currentDragTarget = null; @@ -164,13 +165,22 @@ public class CardDraggerGlassPane implements MouseListener, MouseMotionListener } @Override - public void mouseClicked(MouseEvent e) {} + public void mouseClicked(MouseEvent e) { + } + @Override - public void mousePressed(MouseEvent e) {} + public void mousePressed(MouseEvent e) { + } + @Override - public void mouseEntered(MouseEvent e) {} + public void mouseEntered(MouseEvent e) { + } + @Override - public void mouseExited(MouseEvent e) {} + public void mouseExited(MouseEvent e) { + } + @Override - public void mouseMoved(MouseEvent e) {} + public void mouseMoved(MouseEvent e) { + } } diff --git a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java index 4a094c6655..bd4b6dbf76 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java @@ -1,430 +1,430 @@ /* - * CardGrid.java - * - * Created on 30-Mar-2010, 9:25:40 PM - */ -package mage.client.cards; + * CardGrid.java + * + * Created on 30-Mar-2010, 9:25:40 PM + */ + package mage.client.cards; -import mage.cards.MageCard; -import mage.client.deckeditor.SortSetting; -import mage.client.plugins.impl.Plugins; -import mage.client.util.ClientEventType; -import mage.client.util.Event; -import mage.client.util.GUISizeHelper; -import mage.client.util.Listener; -import mage.utils.CardColorUtil; -import mage.view.CardView; -import mage.view.CardsView; -import org.mage.card.arcane.CardPanel; + import mage.cards.MageCard; + import mage.client.deckeditor.SortSetting; + import mage.client.dialog.PreferencesDialog; + import mage.client.plugins.impl.Plugins; + import mage.client.util.ClientEventType; + import mage.client.util.Event; + import mage.client.util.GUISizeHelper; + import mage.client.util.Listener; + import mage.utils.CardColorUtil; + import mage.view.CardView; + import mage.view.CardsView; + import org.mage.card.arcane.CardPanel; -import java.awt.*; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.*; -import java.util.List; -import java.util.Map.Entry; + import java.awt.*; + import java.awt.event.MouseEvent; + import java.awt.event.MouseListener; + import java.util.List; + import java.util.*; + import java.util.Map.Entry; -/** - * - * @author BetaSteward_at_googlemail.com - */ -public class CardGrid extends javax.swing.JLayeredPane implements MouseListener, ICardGrid { + /** + * @author BetaSteward_at_googlemail.com + */ + public class CardGrid extends javax.swing.JLayeredPane implements MouseListener, ICardGrid { - protected final CardEventSource cardEventSource = new CardEventSource(); - protected BigCard bigCard; - protected UUID gameId; - private final Map cards = new HashMap<>(); - private Dimension cardDimension; + protected final CardEventSource cardEventSource = new CardEventSource(); + protected BigCard bigCard; + protected UUID gameId; + private final Map cards = new HashMap<>(); + private Dimension cardDimension; - /** - * Max amount of cards in card grid for which card images will be drawn. - * Done so to solve issue with memory for big piles of cards. - */ - public static final int MAX_IMAGES = 350; + /** + * Max amount of cards in card grid for which card images will be drawn. + * Done so to solve issue with memory for big piles of cards. + */ + public static final int MAX_IMAGES = 350; - public CardGrid() { - initComponents(); - setGUISize(); - setOpaque(false); - } + public CardGrid() { + initComponents(); + setGUISize(); + setOpaque(false); + } - public void clear() { - for (MouseListener ml : this.getMouseListeners()) { - this.removeMouseListener(ml); - } - this.clearCardEventListeners(); - this.clearCards(); - this.bigCard = null; - } + public void clear() { + for (MouseListener ml : this.getMouseListeners()) { + this.removeMouseListener(ml); + } + this.clearCardEventListeners(); + this.clearCards(); + this.bigCard = null; + } - public void changeGUISize() { - setGUISize(); - } + public void changeGUISize() { + setGUISize(); + } - private void setGUISize() { - cardDimension = GUISizeHelper.editorCardDimension; - } + private void setGUISize() { + cardDimension = GUISizeHelper.editorCardDimension; + } - @Override - public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId) { - this.loadCards(showCards, sortSetting, bigCard, gameId, true); - } + @Override + public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId) { + this.loadCards(showCards, sortSetting, bigCard, gameId, true); + } - @Override - public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId, boolean merge) { - boolean drawImage = showCards.size() <= MAX_IMAGES; - this.bigCard = bigCard; - this.gameId = gameId; - if (merge) { - for (CardView card : showCards.values()) { - if (!cards.containsKey(card.getId())) { - addCard(card, bigCard, gameId, drawImage); - } - } - for (Iterator> i = cards.entrySet().iterator(); i.hasNext();) { - Entry entry = i.next(); - if (!showCards.containsKey(entry.getKey())) { - removeCardImg(entry.getKey()); - i.remove(); - } - } - } else { - this.clearCards(); - for (CardView card : showCards.values()) { - addCard(card, bigCard, gameId, drawImage); - } - } - drawCards(sortSetting); - this.setVisible(true); - } + @Override + public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId, boolean merge) { + boolean drawImage = showCards.size() <= MAX_IMAGES; + this.bigCard = bigCard; + this.gameId = gameId; + if (merge) { + for (CardView card : showCards.values()) { + if (!cards.containsKey(card.getId())) { + addCard(card, bigCard, gameId, drawImage); + } + } + for (Iterator> i = cards.entrySet().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (!showCards.containsKey(entry.getKey())) { + removeCardImg(entry.getKey()); + i.remove(); + } + } + } else { + this.clearCards(); + for (CardView card : showCards.values()) { + addCard(card, bigCard, gameId, drawImage); + } + } + drawCards(sortSetting); + this.setVisible(true); + } - private void addCard(CardView card, BigCard bigCard, UUID gameId, boolean drawImage) { - MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, drawImage, true); - cards.put(card.getId(), cardImg); - cardImg.addMouseListener(this); - add(cardImg); - cardImg.update(card); - cards.put(card.getId(), cardImg); - } + private void addCard(CardView card, BigCard bigCard, UUID gameId, boolean drawImage) { + MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, drawImage, true, PreferencesDialog.getRenderMode()); + cards.put(card.getId(), cardImg); + cardImg.addMouseListener(this); + add(cardImg); + cardImg.update(card); + cards.put(card.getId(), cardImg); + } - @Override - public void drawCards(SortSetting sortSetting) { - int maxWidth = this.getParent().getWidth(); - int cardVerticalOffset = GUISizeHelper.editorCardOffsetSize; - int numColumns = maxWidth / cardDimension.width; - int curColumn = 0; - int curRow = 0; - if (!cards.isEmpty()) { - Rectangle rectangle = new Rectangle(cardDimension.width, cardDimension.height); - List sortedCards = new ArrayList<>(cards.values()); - switch (sortSetting.getSortBy()) { - case NAME: - sortedCards.sort(new CardNameComparator()); - break; - case CARD_TYPE: - sortedCards.sort(new CardTypeComparator()); - break; - case RARITY: - sortedCards.sort(new CardRarityComparator()); - break; - case COLOR: - sortedCards.sort(new CardColorComparator()); - break; - case COLOR_IDENTITY: - sortedCards.sort(new CardColorDetailedIdentity()); - break; - case CASTING_COST: - sortedCards.sort(new CardCostComparator()); - break; + @Override + public void drawCards(SortSetting sortSetting) { + int maxWidth = this.getParent().getWidth(); + int cardVerticalOffset = GUISizeHelper.editorCardOffsetSize; + int numColumns = maxWidth / cardDimension.width; + int curColumn = 0; + int curRow = 0; + if (!cards.isEmpty()) { + Rectangle rectangle = new Rectangle(cardDimension.width, cardDimension.height); + List sortedCards = new ArrayList<>(cards.values()); + switch (sortSetting.getSortBy()) { + case NAME: + sortedCards.sort(new CardNameComparator()); + break; + case CARD_TYPE: + sortedCards.sort(new CardTypeComparator()); + break; + case RARITY: + sortedCards.sort(new CardRarityComparator()); + break; + case COLOR: + sortedCards.sort(new CardColorComparator()); + break; + case COLOR_IDENTITY: + sortedCards.sort(new CardColorDetailedIdentity()); + break; + case CASTING_COST: + sortedCards.sort(new CardCostComparator()); + break; - } - MageCard lastCard = null; - for (MageCard cardImg : sortedCards) { - if (sortSetting.isPilesToggle()) { - if (lastCard == null) { - lastCard = cardImg; - } - switch (sortSetting.getSortBy()) { - case NAME: - if (!cardImg.getOriginal().getName().equals(lastCard.getOriginal().getName())) { - curColumn++; - curRow = 0; - } - break; - case CARD_TYPE: - if (!cardImg.getOriginal().getCardTypes().equals(lastCard.getOriginal().getCardTypes())) { - curColumn++; - curRow = 0; - } - break; - case RARITY: - if (cardImg.getOriginal().getRarity() != lastCard.getOriginal().getRarity()) { - curColumn++; - curRow = 0; - } - break; - case COLOR: - if (cardImg.getOriginal().getColor().compareTo(lastCard.getOriginal().getColor()) != 0) { - curColumn++; - curRow = 0; - } - break; - case COLOR_IDENTITY: - if (CardColorUtil.getColorIdentitySortValue(cardImg.getOriginal().getManaCost(), cardImg.getOriginal().getColor(), cardImg.getOriginal().getRules()) - != CardColorUtil.getColorIdentitySortValue(lastCard.getOriginal().getManaCost(), lastCard.getOriginal().getColor(), lastCard.getOriginal().getRules())) { - curColumn++; - curRow = 0; - } - break; - case CASTING_COST: - if (cardImg.getOriginal().getConvertedManaCost() != lastCard.getOriginal().getConvertedManaCost()) { - curColumn++; - curRow = 0; - } - break; - } - rectangle.setLocation(curColumn * cardDimension.width, curRow * cardVerticalOffset); - cardImg.setBounds(rectangle); - cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); - moveToFront(cardImg); - curRow++; - lastCard = cardImg; - } else { - rectangle.setLocation(curColumn * cardDimension.width, curRow * cardVerticalOffset); - cardImg.setBounds(rectangle); - cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); - moveToFront(cardImg); - curColumn++; - if (curColumn == numColumns) { - curColumn = 0; - curRow++; - } - } - } - } - resizeArea(); - revalidate(); - repaint(); - } + } + MageCard lastCard = null; + for (MageCard cardImg : sortedCards) { + if (sortSetting.isPilesToggle()) { + if (lastCard == null) { + lastCard = cardImg; + } + switch (sortSetting.getSortBy()) { + case NAME: + if (!cardImg.getOriginal().getName().equals(lastCard.getOriginal().getName())) { + curColumn++; + curRow = 0; + } + break; + case CARD_TYPE: + if (!cardImg.getOriginal().getCardTypes().equals(lastCard.getOriginal().getCardTypes())) { + curColumn++; + curRow = 0; + } + break; + case RARITY: + if (cardImg.getOriginal().getRarity() != lastCard.getOriginal().getRarity()) { + curColumn++; + curRow = 0; + } + break; + case COLOR: + if (cardImg.getOriginal().getColor().compareTo(lastCard.getOriginal().getColor()) != 0) { + curColumn++; + curRow = 0; + } + break; + case COLOR_IDENTITY: + if (CardColorUtil.getColorIdentitySortValue(cardImg.getOriginal().getManaCost(), cardImg.getOriginal().getColor(), cardImg.getOriginal().getRules()) + != CardColorUtil.getColorIdentitySortValue(lastCard.getOriginal().getManaCost(), lastCard.getOriginal().getColor(), lastCard.getOriginal().getRules())) { + curColumn++; + curRow = 0; + } + break; + case CASTING_COST: + if (cardImg.getOriginal().getConvertedManaCost() != lastCard.getOriginal().getConvertedManaCost()) { + curColumn++; + curRow = 0; + } + break; + } + rectangle.setLocation(curColumn * cardDimension.width, curRow * cardVerticalOffset); + cardImg.setBounds(rectangle); + cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); + moveToFront(cardImg); + curRow++; + lastCard = cardImg; + } else { + rectangle.setLocation(curColumn * cardDimension.width, curRow * cardVerticalOffset); + cardImg.setBounds(rectangle); + cardImg.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); + moveToFront(cardImg); + curColumn++; + if (curColumn == numColumns) { + curColumn = 0; + curRow++; + } + } + } + } + resizeArea(); + revalidate(); + repaint(); + } - private void clearCards() { - // remove possible mouse listeners, preventing gc - for (MageCard mageCard : cards.values()) { - if (mageCard instanceof CardPanel) { - ((CardPanel) mageCard).cleanUp(); - } - } - this.cards.clear(); - removeAllCardImg(); - } + private void clearCards() { + // remove possible mouse listeners, preventing gc + for (MageCard mageCard : cards.values()) { + if (mageCard instanceof CardPanel) { + ((CardPanel) mageCard).cleanUp(); + } + } + this.cards.clear(); + removeAllCardImg(); + } - private void removeAllCardImg() { - for (Component comp : getComponents()) { - if (comp instanceof Card || comp instanceof MageCard) { - remove(comp); - } - } - } + private void removeAllCardImg() { + for (Component comp : getComponents()) { + if (comp instanceof Card || comp instanceof MageCard) { + remove(comp); + } + } + } - private void removeCardImg(UUID cardId) { - for (Component comp : getComponents()) { - if (comp instanceof Card) { - if (((Card) comp).getCardId().equals(cardId)) { - remove(comp); - comp = null; - } - } else if (comp instanceof MageCard) { - if (((MageCard) comp).getOriginal().getId().equals(cardId)) { - remove(comp); - comp = null; - } - } - } - } + private void removeCardImg(UUID cardId) { + for (Component comp : getComponents()) { + if (comp instanceof Card) { + if (((Card) comp).getCardId().equals(cardId)) { + remove(comp); + comp = null; + } + } else if (comp instanceof MageCard) { + if (((MageCard) comp).getOriginal().getId().equals(cardId)) { + remove(comp); + comp = null; + } + } + } + } - public void removeCard(UUID cardId) { - removeCardImg(cardId); - cards.remove(cardId); - } + public void removeCard(UUID cardId) { + removeCardImg(cardId); + cards.remove(cardId); + } - @Override - public void addCardEventListener(Listener listener) { - cardEventSource.addListener(listener); - } + @Override + public void addCardEventListener(Listener listener) { + cardEventSource.addListener(listener); + } - @Override - public void clearCardEventListeners() { - cardEventSource.clearListeners(); - } + @Override + public void clearCardEventListeners() { + cardEventSource.clearListeners(); + } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 294, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 197, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 294, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 197, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - @Override - public void mouseClicked(MouseEvent e) { - if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) { // double clicks and repeated double clicks - e.consume(); - Object obj = e.getSource(); - if (obj instanceof Card) { - if (e.isAltDown()) { - cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); - } else { - cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); - } - } else if (obj instanceof MageCard) { - if (e.isAltDown()) { - cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); - } else { - cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); - } - } - } - } + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + @Override + public void mouseClicked(MouseEvent e) { + if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) { // double clicks and repeated double clicks + e.consume(); + Object obj = e.getSource(); + if (obj instanceof Card) { + if (e.isAltDown()) { + cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); + } else { + cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); + } + } else if (obj instanceof MageCard) { + if (e.isAltDown()) { + cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); + } else { + cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); + } + } + } + } - @Override - public void mousePressed(MouseEvent e) { - } + @Override + public void mousePressed(MouseEvent e) { + } - @Override - public void mouseReleased(MouseEvent e) { - } + @Override + public void mouseReleased(MouseEvent e) { + } - @Override - public void mouseEntered(MouseEvent e) { - } + @Override + public void mouseEntered(MouseEvent e) { + } - @Override - public void mouseExited(MouseEvent e) { - } + @Override + public void mouseExited(MouseEvent e) { + } - private void resizeArea() { - Dimension area = new Dimension(0, 0); - Dimension size = getPreferredSize(); + private void resizeArea() { + Dimension area = new Dimension(0, 0); + Dimension size = getPreferredSize(); - for (Component comp : getComponents()) { - Rectangle r = comp.getBounds(); - if (r.x + r.width > area.width) { - area.width = r.x + r.width; - } - if (r.y + r.height > area.height) { - area.height = r.y + r.height; - } - } - if (size.height != area.height || size.width != area.width) { - setPreferredSize(area); - } - } + for (Component comp : getComponents()) { + Rectangle r = comp.getBounds(); + if (r.x + r.width > area.width) { + area.width = r.x + r.width; + } + if (r.y + r.height > area.height) { + area.height = r.y + r.height; + } + } + if (size.height != area.height || size.width != area.width) { + setPreferredSize(area); + } + } - @Override - public void refresh() { - revalidate(); - repaint(); - } + @Override + public void refresh() { + revalidate(); + repaint(); + } - @Override - public int cardsSize() { - return cards.size(); - } -} + @Override + public int cardsSize() { + return cards.size(); + } + } -class CardNameComparator implements Comparator { + class CardNameComparator implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } + @Override + public int compare(MageCard o1, MageCard o2) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } -} + } -class CardRarityComparator implements Comparator { + class CardRarityComparator implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - int val = o1.getOriginal().getRarity().compareTo(o2.getOriginal().getRarity()); - if (val == 0) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } else { - return val; - } - } + @Override + public int compare(MageCard o1, MageCard o2) { + int val = o1.getOriginal().getRarity().compareTo(o2.getOriginal().getRarity()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } else { + return val; + } + } -} + } -class CardCostComparator implements Comparator { + class CardCostComparator implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - int val = Integer.valueOf(o1.getOriginal().getConvertedManaCost()).compareTo(o2.getOriginal().getConvertedManaCost()); - if (val == 0) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } else { - return val; - } - } + @Override + public int compare(MageCard o1, MageCard o2) { + int val = Integer.valueOf(o1.getOriginal().getConvertedManaCost()).compareTo(o2.getOriginal().getConvertedManaCost()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } else { + return val; + } + } -} + } -class CardColorComparator implements Comparator { + class CardColorComparator implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - int val = o1.getOriginal().getColor().compareTo(o2.getOriginal().getColor()); - if (val == 0) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } else { - return val; - } - } + @Override + public int compare(MageCard o1, MageCard o2) { + int val = o1.getOriginal().getColor().compareTo(o2.getOriginal().getColor()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } else { + return val; + } + } -} + } -class CardColorDetailedIdentity implements Comparator { + class CardColorDetailedIdentity implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - int val = CardColorUtil.getColorIdentitySortValue(o1.getOriginal().getManaCost(), o1.getOriginal().getColor(), o1.getOriginal().getRules()) - - CardColorUtil.getColorIdentitySortValue(o2.getOriginal().getManaCost(), o2.getOriginal().getColor(), o2.getOriginal().getRules()); - if (val == 0) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } else { - return val; - } - } + @Override + public int compare(MageCard o1, MageCard o2) { + int val = CardColorUtil.getColorIdentitySortValue(o1.getOriginal().getManaCost(), o1.getOriginal().getColor(), o1.getOriginal().getRules()) + - CardColorUtil.getColorIdentitySortValue(o2.getOriginal().getManaCost(), o2.getOriginal().getColor(), o2.getOriginal().getRules()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } else { + return val; + } + } -} + } -class CardTypeComparator implements Comparator { + class CardTypeComparator implements Comparator { - @Override - public int compare(MageCard o1, MageCard o2) { - int val = o1.getOriginal().getCardTypes().toString().compareTo(o2.getOriginal().getCardTypes().toString()); - if (val == 0) { - return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); - } else { - return val; - } - } + @Override + public int compare(MageCard o1, MageCard o2) { + int val = o1.getOriginal().getCardTypes().toString().compareTo(o2.getOriginal().getCardTypes().toString()); + if (val == 0) { + return o1.getOriginal().getName().compareTo(o2.getOriginal().getName()); + } else { + return val; + } + } -} + } diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index 32fa2a2060..8c7f885d25 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -8,6 +8,7 @@ package mage.client.cards; import mage.cards.MageCard; + import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.CardsViewUtil; import mage.client.util.Config; @@ -227,7 +228,7 @@ } private void addCard(CardView card, BigCard bigCard, UUID gameId) { - MageCard mageCard = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true); + MageCard mageCard = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true, PreferencesDialog.getRenderMode()); if (zone != null) { mageCard.setZone(zone); } diff --git a/Mage.Client/src/main/java/mage/client/cards/CardsList.java b/Mage.Client/src/main/java/mage/client/cards/CardsList.java index 857a8cf286..3384cfe8a2 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardsList.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardsList.java @@ -1,709 +1,709 @@ /* - * CardsList.java - * - * Created on Dec 18, 2009, 10:40:12 AM - */ -package mage.client.cards; - -import mage.cards.MageCard; -import mage.client.constants.Constants.DeckEditorMode; -import mage.client.constants.Constants.SortBy; -import mage.client.deckeditor.SortSetting; -import mage.client.deckeditor.table.TableModel; -import mage.client.deckeditor.table.UpdateCountsCallback; -import mage.client.dialog.PreferencesDialog; -import mage.client.plugins.impl.Plugins; -import mage.client.util.*; -import mage.client.util.Event; -import mage.client.util.gui.TableSpinnerEditor; -import mage.view.CardView; -import mage.view.CardsView; -import mage.view.SimpleCardView; -import org.mage.card.arcane.CardPanel; -import org.mage.card.arcane.ManaSymbolsCellRenderer; - -import javax.swing.*; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.beans.Beans; -import java.util.*; -import java.util.List; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class CardsList extends javax.swing.JPanel implements MouseListener, ICardGrid { - - protected final CardEventSource cardEventSource = new CardEventSource(); - private Dimension cardDimension; - private int rowHeight; - private CardsView cards; - private Map mageCards = new LinkedHashMap<>(); - protected BigCard bigCard; - protected UUID gameId; - private SortSetting sortSetting; - - private TableModel mainModel; - private JTable mainTable; - private ICardGrid currentView; - - /** - * Creates new form Cards - */ - public CardsList() { - initComponents(); - makeTransparent(); - initListViewComponents(); - setGUISize(); - } - - public void cleanUp() { - this.clearCardEventListeners(); - if (cards != null) { - cards.clear(); - } - if (mainModel != null) { - mainModel.removeTableModelListener(mainTable); - mainModel.clear(); - } - if (cardArea != null) { - for (MouseListener ml : cardArea.getMouseListeners()) { - cardArea.removeMouseListener(ml); - } - for (Component comp : cardArea.getComponents()) { - if (comp instanceof CardPanel) { - ((CardPanel) comp).cleanUp(); - } - } - cardArea.removeAll(); - } - if (mainTable != null) { - for (MouseListener ml : mainTable.getMouseListeners()) { - mainTable.removeMouseListener(ml); - } - } - if (currentView != null) { - currentView.clearCardEventListeners(); - } - - mageCards.clear(); - this.bigCard = null; - - } - - public void changeGUISize() { - setGUISize(); - redrawCards(); - } - - private void setGUISize() { - mainTable.getTableHeader().setFont(GUISizeHelper.tableFont); - mainTable.setFont(GUISizeHelper.tableFont); - mainTable.setRowHeight(GUISizeHelper.getTableRowHeight()); - cardDimension = GUISizeHelper.editorCardDimension; - rowHeight = GUISizeHelper.editorCardOffsetSize; - } - - private void makeTransparent() { - panelCardArea.setOpaque(false); - cardArea.setOpaque(false); - panelCardArea.getViewport().setOpaque(false); - panelControl.setBackground(new Color(250, 250, 250, 150)); - panelControl.setOpaque(true); - cbSortBy.setModel(new DefaultComboBoxModel<>(SortBy.values())); - } - - private void initListViewComponents() { - mainTable = new JTable(); - - mainModel = new TableModel(); - mainModel.addListeners(mainTable); - - mainTable.setModel(mainModel); - mainTable.setForeground(Color.white); - DefaultTableCellRenderer myRenderer = (DefaultTableCellRenderer) mainTable.getDefaultRenderer(String.class); - myRenderer.setBackground(new Color(0, 0, 0, 100)); - mainTable.getColumnModel().getColumn(0).setMaxWidth(25); - mainTable.getColumnModel().getColumn(0).setPreferredWidth(25); - mainTable.getColumnModel().getColumn(1).setPreferredWidth(110); - mainTable.getColumnModel().getColumn(2).setPreferredWidth(90); - mainTable.getColumnModel().getColumn(3).setPreferredWidth(50); - mainTable.getColumnModel().getColumn(4).setPreferredWidth(170); - mainTable.getColumnModel().getColumn(5).setPreferredWidth(30); - mainTable.getColumnModel().getColumn(6).setPreferredWidth(15); - mainTable.getColumnModel().getColumn(7).setPreferredWidth(15); - - // new mana render (svg support) - mainTable.getColumnModel().getColumn(mainModel.COLUMN_INDEX_COST).setCellRenderer(new ManaSymbolsCellRenderer()); - - if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView").equals("listView")) { - jToggleListView.setSelected(true); - panelCardArea.setViewportView(mainTable); - currentView = mainModel; - cbSortBy.setEnabled(false); - chkPiles.setEnabled(false); - } else { - jToggleCardView.setSelected(true); - currentView = this; - panelCardArea.setViewportView(cardArea); - cbSortBy.setEnabled(true); - chkPiles.setEnabled(true); - } - - cardArea.addMouseListener(this); - - mainTable.setOpaque(false); - mainTable.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) { // double clicks and repeated double clicks - e.consume(); - if (e.isAltDown()) { - handleAltDoubleClick(); - } else { - handleDoubleClick(); - } - } - } - }); - - mainModel.setUpdateCountsCallback(new UpdateCountsCallback(lblCount, lblCreatureCount, lblLandCount, null, null, null, null)); - } - - // if you use the deck ediot to build a free deck, numbers can be set directly in deck and sideboard - public void setDeckEditorMode(DeckEditorMode mode) { - if (mode == DeckEditorMode.FREE_BUILDING) { - // activate spinner for card number change - mainModel.setNumberEditable(true); - TableColumnModel tcm = mainTable.getColumnModel(); - TableColumn tc = tcm.getColumn(0); - tc.setMaxWidth(55); - tc.setMinWidth(55); - tc.setPreferredWidth(55); - tc.setCellEditor(new TableSpinnerEditor(this)); - } - } - - public void handleSetNumber(int number) { - if (mainTable.getSelectedRowCount() == 1) { - mainModel.setNumber(mainTable.getSelectedRow(), number); - } - } - - public void handleDoubleClick() { - if (mainTable.getSelectedRowCount() > 0) { - int[] n = mainTable.getSelectedRows(); - List indexes = asList(n); - Collections.reverse(indexes); - for (Integer index : indexes) { - mainModel.doubleClick(index); - } - } - } - - public void handleAltDoubleClick() { - if (mainTable.getSelectedRowCount() > 0) { - int[] n = mainTable.getSelectedRows(); - List indexes = asList(n); - Collections.reverse(indexes); - for (Integer index : indexes) { - mainModel.altDoubleClick(index); - } - } - } - - public ICardGrid getMainModel() { - return mainModel; - } - - public List asList(final int[] is) { - List list = new ArrayList<>(); - for (int i : is) { - list.add(i); - } - return list; - } - - public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId) { - int selectedRow = -1; - if (currentView.equals(mainModel)) { - selectedRow = mainTable.getSelectedRow(); - } - this.cards = showCards; - this.bigCard = bigCard; - this.gameId = gameId; - - cbSortBy.setSelectedItem(sortSetting.getSortBy()); - chkPiles.setSelected(sortSetting.isPilesToggle()); - currentView.loadCards(showCards, sortSetting, bigCard, gameId); - if (selectedRow >= 0) { - selectedRow = Math.min(selectedRow, mainTable.getRowCount() - 1); - if (selectedRow >= 0) { - mainTable.setRowSelectionInterval(selectedRow, selectedRow); - } - } - } - - private void redrawCards() { - if (cards == null) { - cards = new CardsView(); - } - currentView.loadCards(cards, sortSetting, bigCard, gameId); - } - - @Override - public void drawCards(SortSetting sortSetting) { - int maxWidth = this.getParent().getWidth(); - int numColumns = maxWidth / cardDimension.width; - int curColumn = 0; - int curRow = 0; - int maxRow = 0; - int maxColumn = 0; - Comparator comparator = null; - Map oldMageCards = mageCards; - mageCards = new LinkedHashMap<>(); - - //Find card view - for (Map.Entry view : cards.entrySet()) { - UUID uuid = view.getKey(); - CardView cardView = view.getValue(); - if (oldMageCards.containsKey(uuid)) { - mageCards.put(uuid, oldMageCards.get(uuid)); - oldMageCards.remove(uuid); - } else { - mageCards.put(uuid, addCard(cardView, bigCard, gameId)); - } - } - //Remove unused cards - for (MageCard card : oldMageCards.values()) { - cardArea.remove(card); - } - - if (cards != null && !cards.isEmpty()) { - Rectangle rectangle = new Rectangle(cardDimension.width, cardDimension.height); - List sortedCards = new ArrayList<>(cards.values()); - switch (sortSetting.getSortBy()) { - case NAME: - comparator = new CardViewNameComparator(); - break; - case RARITY: - comparator = new CardViewRarityComparator(); - break; - case CARD_TYPE: - comparator = new CardViewCardTypeComparator(); - break; - case COLOR: - comparator = new CardViewColorComparator(); - break; - case COLOR_IDENTITY: - comparator = new CardViewColorIdentityComparator(); - break; - case CASTING_COST: - comparator = new CardViewCostComparator(); - break; - } - if (comparator != null) { - sortedCards.sort(new CardViewNameComparator()); - sortedCards.sort(comparator); - } - CardView lastCard = null; - for (CardView card : sortedCards) { - if (sortSetting.isPilesToggle()) { - if (lastCard == null) { - lastCard = card; - } - if (comparator != null) { - if (comparator.compare(card, lastCard) > 0) { - curColumn++; - maxRow = Math.max(maxRow, curRow); - curRow = 0; - } - } - rectangle.setLocation(curColumn * cardDimension.width, curRow * rowHeight); - setCardBounds(mageCards.get(card.getId()), rectangle); - - curRow++; - lastCard = card; - } else { - rectangle.setLocation(curColumn * cardDimension.width, curRow * rowHeight); - setCardBounds(mageCards.get(card.getId()), rectangle); - curColumn++; - if (curColumn == numColumns) { - maxColumn = Math.max(maxColumn, curColumn); - curColumn = 0; - curRow++; - } - } - } - } - maxRow = Math.max(maxRow, curRow); - maxColumn = Math.max(maxColumn, curColumn); - updateCounts(); - cardArea.setPreferredSize(new Dimension((maxColumn + 1) * cardDimension.width, cardDimension.height + maxRow * rowHeight)); - cardArea.revalidate(); - this.revalidate(); - this.repaint(); - this.setVisible(true); - } - - private void updateCounts() { - int landCount = 0; - int creatureCount = 0; - int sorceryCount = 0; - int instantCount = 0; - int enchantmentCount = 0; - int artifactCount = 0; - - for (CardView card : cards.values()) { - if (card.isLand()) { - landCount++; - } - if (card.isCreature()) { - creatureCount++; - } - if (card.isSorcery()) { - sorceryCount++; - } - if (card.isInstant()) { - instantCount++; - } - if (card.isEnchantment()) { - enchantmentCount++; - } - if (card.isArtifact()) { - artifactCount++; - } - } - - int count = cards != null ? cards.size() : 0; - this.lblCount.setText(Integer.toString(count)); - this.lblCreatureCount.setText(Integer.toString(creatureCount)); - this.lblLandCount.setText(Integer.toString(landCount)); - } - - private MageCard addCard(CardView card, BigCard bigCard, UUID gameId) { - MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true); - cardArea.add(cardImg); - cardImg.update(card); - cardImg.addMouseListener(this); - return cardImg; - } - - private void setCardBounds(MageCard card, Rectangle rectangle) { - card.setBounds(rectangle); - card.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); - cardArea.moveToFront(card); - } - - @Override - public void addCardEventListener(Listener listener) { - cardEventSource.addListener(listener); - mainModel.addCardEventListener(listener); - } - - @Override - public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId) { - this.loadCards(showCards, sortSetting, bigCard, gameId, true); - } - - @Override - public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId, boolean merge) { - cards = showCards; - this.bigCard = bigCard; - this.gameId = gameId; - drawCards(sortSetting); - } - - @Override - public void refresh() { - redrawCards(); - } - - @Override - public void clearCardEventListeners() { - cardEventSource.clearListeners(); - mainModel.clearCardEventListeners(); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - bgView = new javax.swing.ButtonGroup(); - panelControl = new javax.swing.JPanel(); - lblCount = new javax.swing.JLabel(); - lblLandCount = new javax.swing.JLabel(); - lblCreatureCount = new javax.swing.JLabel(); - chkPiles = new javax.swing.JCheckBox(); - cbSortBy = new javax.swing.JComboBox(); - jToggleListView = new javax.swing.JToggleButton(); - jToggleCardView = new javax.swing.JToggleButton(); - panelCardArea = new javax.swing.JScrollPane(); - cardArea = new javax.swing.JLayeredPane(); - - setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); - setMinimumSize(new java.awt.Dimension(30, 30)); - setPreferredSize((!Beans.isDesignTime()) ? - (GUISizeHelper.editorCardDimension) - : (new Dimension(600, 600))); - setRequestFocusEnabled(false); - - panelControl.setMaximumSize(new java.awt.Dimension(32767, 23)); - panelControl.setMinimumSize(new java.awt.Dimension(616, 23)); - panelControl.setName(""); // NOI18N - panelControl.setPreferredSize(new java.awt.Dimension(616, 23)); - panelControl.setRequestFocusEnabled(false); - - lblCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/deck_pack.png"))); // NOI18N - lblCount.setText("999"); - lblCount.setToolTipText("Number of all cards in this area."); - lblCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - lblCount.setFocusable(false); - lblCount.setInheritsPopupMenu(false); - lblCount.setRequestFocusEnabled(false); - lblCount.setVerifyInputWhenFocusTarget(false); - - lblLandCount.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - lblLandCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/type_land.png"))); // NOI18N - lblLandCount.setText("999"); - lblLandCount.setToolTipText("Number of lands."); - lblLandCount.setVerticalAlignment(javax.swing.SwingConstants.TOP); - lblLandCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - lblLandCount.setFocusable(false); - lblLandCount.setInheritsPopupMenu(false); - lblLandCount.setRequestFocusEnabled(false); - lblLandCount.setVerifyInputWhenFocusTarget(false); - - lblCreatureCount.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - lblCreatureCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/type_creatures.png"))); // NOI18N - lblCreatureCount.setText("999"); - lblCreatureCount.setToolTipText("Number of creatures."); - lblCreatureCount.setVerticalAlignment(javax.swing.SwingConstants.TOP); - lblCreatureCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - lblCreatureCount.setFocusable(false); - lblCreatureCount.setInheritsPopupMenu(false); - lblCreatureCount.setRequestFocusEnabled(false); - lblCreatureCount.setVerifyInputWhenFocusTarget(false); - - chkPiles.setText("Piles"); - chkPiles.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - chkPiles.setMargin(new java.awt.Insets(3, 2, 2, 2)); - chkPiles.addActionListener(evt -> chkPilesActionPerformed(evt)); - - cbSortBy.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"SortBy"})); - cbSortBy.setToolTipText("Sort the cards if card view is active."); - cbSortBy.setMaximumSize(new java.awt.Dimension(120, 20)); - cbSortBy.setMinimumSize(new java.awt.Dimension(120, 20)); - cbSortBy.setName("SortBy"); // NOI18N - cbSortBy.setOpaque(false); - cbSortBy.setPreferredSize(new java.awt.Dimension(120, 20)); - cbSortBy.addActionListener(evt -> cbSortByActionPerformed(evt)); - - bgView.add(jToggleListView); - jToggleListView.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/list_panel.png"))); // NOI18N - jToggleListView.setToolTipText("Shows the cards as a list."); - jToggleListView.setBorder(null); - jToggleListView.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); - jToggleListView.setMargin(new java.awt.Insets(2, 6, 2, 6)); - jToggleListView.setMaximumSize(new java.awt.Dimension(37, 25)); - jToggleListView.setMinimumSize(new java.awt.Dimension(37, 25)); - jToggleListView.setPreferredSize(new java.awt.Dimension(44, 22)); - jToggleListView.addActionListener(evt -> jToggleListViewActionPerformed(evt)); - - bgView.add(jToggleCardView); - jToggleCardView.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/card_panel.png"))); // NOI18N - jToggleCardView.setToolTipText("Shows the card as images."); - jToggleCardView.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); - jToggleCardView.setMargin(new java.awt.Insets(2, 6, 2, 6)); - jToggleCardView.setPreferredSize(new java.awt.Dimension(40, 22)); - jToggleCardView.addActionListener(evt -> jToggleCardViewActionPerformed(evt)); - - javax.swing.GroupLayout panelControlLayout = new javax.swing.GroupLayout(panelControl); - panelControl.setLayout(panelControlLayout); - panelControlLayout.setHorizontalGroup( - panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelControlLayout.createSequentialGroup() - .addComponent(lblCount) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lblLandCount) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lblCreatureCount) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(chkPiles) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(cbSortBy, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jToggleListView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jToggleCardView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - panelControlLayout.setVerticalGroup( - panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelControlLayout.createSequentialGroup() - .addGroup(panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lblCount) - .addComponent(lblLandCount) - .addComponent(lblCreatureCount) - .addComponent(chkPiles)) - .addComponent(cbSortBy, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jToggleListView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jToggleCardView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 0, 0)) - ); - - jToggleListView.getAccessibleContext().setAccessibleDescription("Switch between image and table view."); - - panelCardArea.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); - panelCardArea.setViewportView(cardArea); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(panelControl, javax.swing.GroupLayout.PREFERRED_SIZE, 467, Short.MAX_VALUE) - .addComponent(panelCardArea) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(panelControl, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(2, 2, 2) - .addComponent(panelCardArea, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE)) - ); - }// //GEN-END:initComponents - - private void jToggleCardViewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleCardViewActionPerformed - currentView = this; - panelCardArea.setViewportView(cardArea); - cbSortBy.setEnabled(true); - chkPiles.setEnabled(true); - PreferencesDialog.saveValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView"); - redrawCards(); - }//GEN-LAST:event_jToggleCardViewActionPerformed - - private void jToggleListViewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleListViewActionPerformed - currentView = mainModel; - panelCardArea.setViewportView(mainTable); - cbSortBy.setEnabled(false); - chkPiles.setEnabled(false); - PreferencesDialog.saveValue(PreferencesDialog.KEY_DRAFT_VIEW, "listView"); - redrawCards(); - }//GEN-LAST:event_jToggleListViewActionPerformed - - private void cbSortByActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbSortByActionPerformed - sortSetting.setSortBy((SortBy) cbSortBy.getSelectedItem()); - drawCards(sortSetting); - }//GEN-LAST:event_cbSortByActionPerformed - - private void chkPilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkPilesActionPerformed - sortSetting.setPilesToggle(chkPiles.isSelected()); - drawCards(sortSetting); - }//GEN-LAST:event_chkPilesActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.ButtonGroup bgView; - private javax.swing.JLayeredPane cardArea; - private javax.swing.JComboBox cbSortBy; - private javax.swing.JCheckBox chkPiles; - private javax.swing.JToggleButton jToggleCardView; - private javax.swing.JToggleButton jToggleListView; - private javax.swing.JLabel lblCount; - private javax.swing.JLabel lblCreatureCount; - private javax.swing.JLabel lblLandCount; - private javax.swing.JScrollPane panelCardArea; - private javax.swing.JPanel panelControl; - // End of variables declaration//GEN-END:variables - - @Override - public void mouseClicked(MouseEvent e) { - } - - @Override - public void mousePressed(MouseEvent e) { - if (e.getClickCount() >= 1 && !e.isConsumed()) { - Object obj = e.getSource(); - if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0)) { // double clicks and repeated double clicks - e.consume(); - if (obj instanceof Card) { - if (e.isAltDown()) { - cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); - } else { - cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); - } - } else if (obj instanceof MageCard) { - if (e.isAltDown()) { - cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); - } else { - cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); - } - } - } - if (obj instanceof MageCard) { - checkMenu(e, ((MageCard) obj).getOriginal()); - } else { - checkMenu(e, null); - } - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (!e.isConsumed()) { - Object obj = e.getSource(); - if (obj instanceof MageCard) { - checkMenu(e, ((MageCard) obj).getOriginal()); - } else { - checkMenu(e, null); - } - } - } - - private void checkMenu(MouseEvent Me, SimpleCardView card) { - if (Me.isPopupTrigger()) { - Me.consume(); - cardEventSource.fireEvent(card, Me.getComponent(), Me.getX(), Me.getY(), ClientEventType.SHOW_POP_UP_MENU); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - } - - public void setDisplayNoCopies(boolean value) { - mainModel.setDisplayNoCopies(value); - } - - @Override - public int cardsSize() { - return cards.size(); - } - - public void setSortBy(SortBy sortBy) { - if (sortBy != null) { - cbSortBy.setSelectedIndex(sortBy.ordinal()); - } - } - - public void setSortSetting(SortSetting sortSetting) { - this.sortSetting = sortSetting; - } - -} + * CardsList.java + * + * Created on Dec 18, 2009, 10:40:12 AM + */ + package mage.client.cards; + + import mage.cards.MageCard; + import mage.client.constants.Constants.DeckEditorMode; + import mage.client.constants.Constants.SortBy; + import mage.client.deckeditor.SortSetting; + import mage.client.deckeditor.table.TableModel; + import mage.client.deckeditor.table.UpdateCountsCallback; + import mage.client.dialog.PreferencesDialog; + import mage.client.plugins.impl.Plugins; + import mage.client.util.Event; + import mage.client.util.*; + import mage.client.util.gui.TableSpinnerEditor; + import mage.view.CardView; + import mage.view.CardsView; + import mage.view.SimpleCardView; + import org.mage.card.arcane.CardPanel; + import org.mage.card.arcane.ManaSymbolsCellRenderer; + + import javax.swing.*; + import javax.swing.table.DefaultTableCellRenderer; + import javax.swing.table.TableColumn; + import javax.swing.table.TableColumnModel; + import java.awt.*; + import java.awt.event.MouseAdapter; + import java.awt.event.MouseEvent; + import java.awt.event.MouseListener; + import java.beans.Beans; + import java.util.List; + import java.util.*; + + /** + * @author BetaSteward_at_googlemail.com + */ + public class CardsList extends javax.swing.JPanel implements MouseListener, ICardGrid { + + protected final CardEventSource cardEventSource = new CardEventSource(); + private Dimension cardDimension; + private int rowHeight; + private CardsView cards; + private Map mageCards = new LinkedHashMap<>(); + protected BigCard bigCard; + protected UUID gameId; + private SortSetting sortSetting; + + private TableModel mainModel; + private JTable mainTable; + private ICardGrid currentView; + + /** + * Creates new form Cards + */ + public CardsList() { + initComponents(); + makeTransparent(); + initListViewComponents(); + setGUISize(); + } + + public void cleanUp() { + this.clearCardEventListeners(); + if (cards != null) { + cards.clear(); + } + if (mainModel != null) { + mainModel.removeTableModelListener(mainTable); + mainModel.clear(); + } + if (cardArea != null) { + for (MouseListener ml : cardArea.getMouseListeners()) { + cardArea.removeMouseListener(ml); + } + for (Component comp : cardArea.getComponents()) { + if (comp instanceof CardPanel) { + ((CardPanel) comp).cleanUp(); + } + } + cardArea.removeAll(); + } + if (mainTable != null) { + for (MouseListener ml : mainTable.getMouseListeners()) { + mainTable.removeMouseListener(ml); + } + } + if (currentView != null) { + currentView.clearCardEventListeners(); + } + + mageCards.clear(); + this.bigCard = null; + + } + + public void changeGUISize() { + setGUISize(); + redrawCards(); + } + + private void setGUISize() { + mainTable.getTableHeader().setFont(GUISizeHelper.tableFont); + mainTable.setFont(GUISizeHelper.tableFont); + mainTable.setRowHeight(GUISizeHelper.getTableRowHeight()); + cardDimension = GUISizeHelper.editorCardDimension; + rowHeight = GUISizeHelper.editorCardOffsetSize; + } + + private void makeTransparent() { + panelCardArea.setOpaque(false); + cardArea.setOpaque(false); + panelCardArea.getViewport().setOpaque(false); + panelControl.setBackground(new Color(250, 250, 250, 150)); + panelControl.setOpaque(true); + cbSortBy.setModel(new DefaultComboBoxModel<>(SortBy.values())); + } + + private void initListViewComponents() { + mainTable = new JTable(); + + mainModel = new TableModel(); + mainModel.addListeners(mainTable); + + mainTable.setModel(mainModel); + mainTable.setForeground(Color.white); + DefaultTableCellRenderer myRenderer = (DefaultTableCellRenderer) mainTable.getDefaultRenderer(String.class); + myRenderer.setBackground(new Color(0, 0, 0, 100)); + mainTable.getColumnModel().getColumn(0).setMaxWidth(25); + mainTable.getColumnModel().getColumn(0).setPreferredWidth(25); + mainTable.getColumnModel().getColumn(1).setPreferredWidth(110); + mainTable.getColumnModel().getColumn(2).setPreferredWidth(90); + mainTable.getColumnModel().getColumn(3).setPreferredWidth(50); + mainTable.getColumnModel().getColumn(4).setPreferredWidth(170); + mainTable.getColumnModel().getColumn(5).setPreferredWidth(30); + mainTable.getColumnModel().getColumn(6).setPreferredWidth(15); + mainTable.getColumnModel().getColumn(7).setPreferredWidth(15); + + // new mana render (svg support) + mainTable.getColumnModel().getColumn(mainModel.COLUMN_INDEX_COST).setCellRenderer(new ManaSymbolsCellRenderer()); + + if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView").equals("listView")) { + jToggleListView.setSelected(true); + panelCardArea.setViewportView(mainTable); + currentView = mainModel; + cbSortBy.setEnabled(false); + chkPiles.setEnabled(false); + } else { + jToggleCardView.setSelected(true); + currentView = this; + panelCardArea.setViewportView(cardArea); + cbSortBy.setEnabled(true); + chkPiles.setEnabled(true); + } + + cardArea.addMouseListener(this); + + mainTable.setOpaque(false); + mainTable.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) { // double clicks and repeated double clicks + e.consume(); + if (e.isAltDown()) { + handleAltDoubleClick(); + } else { + handleDoubleClick(); + } + } + } + }); + + mainModel.setUpdateCountsCallback(new UpdateCountsCallback(lblCount, lblCreatureCount, lblLandCount, null, null, null, null)); + } + + // if you use the deck ediot to build a free deck, numbers can be set directly in deck and sideboard + public void setDeckEditorMode(DeckEditorMode mode) { + if (mode == DeckEditorMode.FREE_BUILDING) { + // activate spinner for card number change + mainModel.setNumberEditable(true); + TableColumnModel tcm = mainTable.getColumnModel(); + TableColumn tc = tcm.getColumn(0); + tc.setMaxWidth(55); + tc.setMinWidth(55); + tc.setPreferredWidth(55); + tc.setCellEditor(new TableSpinnerEditor(this)); + } + } + + public void handleSetNumber(int number) { + if (mainTable.getSelectedRowCount() == 1) { + mainModel.setNumber(mainTable.getSelectedRow(), number); + } + } + + public void handleDoubleClick() { + if (mainTable.getSelectedRowCount() > 0) { + int[] n = mainTable.getSelectedRows(); + List indexes = asList(n); + Collections.reverse(indexes); + for (Integer index : indexes) { + mainModel.doubleClick(index); + } + } + } + + public void handleAltDoubleClick() { + if (mainTable.getSelectedRowCount() > 0) { + int[] n = mainTable.getSelectedRows(); + List indexes = asList(n); + Collections.reverse(indexes); + for (Integer index : indexes) { + mainModel.altDoubleClick(index); + } + } + } + + public ICardGrid getMainModel() { + return mainModel; + } + + public List asList(final int[] is) { + List list = new ArrayList<>(); + for (int i : is) { + list.add(i); + } + return list; + } + + public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId) { + int selectedRow = -1; + if (currentView.equals(mainModel)) { + selectedRow = mainTable.getSelectedRow(); + } + this.cards = showCards; + this.bigCard = bigCard; + this.gameId = gameId; + + cbSortBy.setSelectedItem(sortSetting.getSortBy()); + chkPiles.setSelected(sortSetting.isPilesToggle()); + currentView.loadCards(showCards, sortSetting, bigCard, gameId); + if (selectedRow >= 0) { + selectedRow = Math.min(selectedRow, mainTable.getRowCount() - 1); + if (selectedRow >= 0) { + mainTable.setRowSelectionInterval(selectedRow, selectedRow); + } + } + } + + private void redrawCards() { + if (cards == null) { + cards = new CardsView(); + } + currentView.loadCards(cards, sortSetting, bigCard, gameId); + } + + @Override + public void drawCards(SortSetting sortSetting) { + int maxWidth = this.getParent().getWidth(); + int numColumns = maxWidth / cardDimension.width; + int curColumn = 0; + int curRow = 0; + int maxRow = 0; + int maxColumn = 0; + Comparator comparator = null; + Map oldMageCards = mageCards; + mageCards = new LinkedHashMap<>(); + + //Find card view + for (Map.Entry view : cards.entrySet()) { + UUID uuid = view.getKey(); + CardView cardView = view.getValue(); + if (oldMageCards.containsKey(uuid)) { + mageCards.put(uuid, oldMageCards.get(uuid)); + oldMageCards.remove(uuid); + } else { + mageCards.put(uuid, addCard(cardView, bigCard, gameId)); + } + } + //Remove unused cards + for (MageCard card : oldMageCards.values()) { + cardArea.remove(card); + } + + if (cards != null && !cards.isEmpty()) { + Rectangle rectangle = new Rectangle(cardDimension.width, cardDimension.height); + List sortedCards = new ArrayList<>(cards.values()); + switch (sortSetting.getSortBy()) { + case NAME: + comparator = new CardViewNameComparator(); + break; + case RARITY: + comparator = new CardViewRarityComparator(); + break; + case CARD_TYPE: + comparator = new CardViewCardTypeComparator(); + break; + case COLOR: + comparator = new CardViewColorComparator(); + break; + case COLOR_IDENTITY: + comparator = new CardViewColorIdentityComparator(); + break; + case CASTING_COST: + comparator = new CardViewCostComparator(); + break; + } + if (comparator != null) { + sortedCards.sort(new CardViewNameComparator()); + sortedCards.sort(comparator); + } + CardView lastCard = null; + for (CardView card : sortedCards) { + if (sortSetting.isPilesToggle()) { + if (lastCard == null) { + lastCard = card; + } + if (comparator != null) { + if (comparator.compare(card, lastCard) > 0) { + curColumn++; + maxRow = Math.max(maxRow, curRow); + curRow = 0; + } + } + rectangle.setLocation(curColumn * cardDimension.width, curRow * rowHeight); + setCardBounds(mageCards.get(card.getId()), rectangle); + + curRow++; + lastCard = card; + } else { + rectangle.setLocation(curColumn * cardDimension.width, curRow * rowHeight); + setCardBounds(mageCards.get(card.getId()), rectangle); + curColumn++; + if (curColumn == numColumns) { + maxColumn = Math.max(maxColumn, curColumn); + curColumn = 0; + curRow++; + } + } + } + } + maxRow = Math.max(maxRow, curRow); + maxColumn = Math.max(maxColumn, curColumn); + updateCounts(); + cardArea.setPreferredSize(new Dimension((maxColumn + 1) * cardDimension.width, cardDimension.height + maxRow * rowHeight)); + cardArea.revalidate(); + this.revalidate(); + this.repaint(); + this.setVisible(true); + } + + private void updateCounts() { + int landCount = 0; + int creatureCount = 0; + int sorceryCount = 0; + int instantCount = 0; + int enchantmentCount = 0; + int artifactCount = 0; + + for (CardView card : cards.values()) { + if (card.isLand()) { + landCount++; + } + if (card.isCreature()) { + creatureCount++; + } + if (card.isSorcery()) { + sorceryCount++; + } + if (card.isInstant()) { + instantCount++; + } + if (card.isEnchantment()) { + enchantmentCount++; + } + if (card.isArtifact()) { + artifactCount++; + } + } + + int count = cards != null ? cards.size() : 0; + this.lblCount.setText(Integer.toString(count)); + this.lblCreatureCount.setText(Integer.toString(creatureCount)); + this.lblLandCount.setText(Integer.toString(landCount)); + } + + private MageCard addCard(CardView card, BigCard bigCard, UUID gameId) { + MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true, PreferencesDialog.getRenderMode()); + cardArea.add(cardImg); + cardImg.update(card); + cardImg.addMouseListener(this); + return cardImg; + } + + private void setCardBounds(MageCard card, Rectangle rectangle) { + card.setBounds(rectangle); + card.setCardBounds(rectangle.x, rectangle.y, cardDimension.width, cardDimension.height); + cardArea.moveToFront(card); + } + + @Override + public void addCardEventListener(Listener listener) { + cardEventSource.addListener(listener); + mainModel.addCardEventListener(listener); + } + + @Override + public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId) { + this.loadCards(showCards, sortSetting, bigCard, gameId, true); + } + + @Override + public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId, boolean merge) { + cards = showCards; + this.bigCard = bigCard; + this.gameId = gameId; + drawCards(sortSetting); + } + + @Override + public void refresh() { + redrawCards(); + } + + @Override + public void clearCardEventListeners() { + cardEventSource.clearListeners(); + mainModel.clearCardEventListeners(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + bgView = new javax.swing.ButtonGroup(); + panelControl = new javax.swing.JPanel(); + lblCount = new javax.swing.JLabel(); + lblLandCount = new javax.swing.JLabel(); + lblCreatureCount = new javax.swing.JLabel(); + chkPiles = new javax.swing.JCheckBox(); + cbSortBy = new javax.swing.JComboBox(); + jToggleListView = new javax.swing.JToggleButton(); + jToggleCardView = new javax.swing.JToggleButton(); + panelCardArea = new javax.swing.JScrollPane(); + cardArea = new javax.swing.JLayeredPane(); + + setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); + setMinimumSize(new java.awt.Dimension(30, 30)); + setPreferredSize((!Beans.isDesignTime()) ? + (GUISizeHelper.editorCardDimension) + : (new Dimension(600, 600))); + setRequestFocusEnabled(false); + + panelControl.setMaximumSize(new java.awt.Dimension(32767, 23)); + panelControl.setMinimumSize(new java.awt.Dimension(616, 23)); + panelControl.setName(""); // NOI18N + panelControl.setPreferredSize(new java.awt.Dimension(616, 23)); + panelControl.setRequestFocusEnabled(false); + + lblCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/deck_pack.png"))); // NOI18N + lblCount.setText("999"); + lblCount.setToolTipText("Number of all cards in this area."); + lblCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + lblCount.setFocusable(false); + lblCount.setInheritsPopupMenu(false); + lblCount.setRequestFocusEnabled(false); + lblCount.setVerifyInputWhenFocusTarget(false); + + lblLandCount.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + lblLandCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/type_land.png"))); // NOI18N + lblLandCount.setText("999"); + lblLandCount.setToolTipText("Number of lands."); + lblLandCount.setVerticalAlignment(javax.swing.SwingConstants.TOP); + lblLandCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + lblLandCount.setFocusable(false); + lblLandCount.setInheritsPopupMenu(false); + lblLandCount.setRequestFocusEnabled(false); + lblLandCount.setVerifyInputWhenFocusTarget(false); + + lblCreatureCount.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + lblCreatureCount.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/type_creatures.png"))); // NOI18N + lblCreatureCount.setText("999"); + lblCreatureCount.setToolTipText("Number of creatures."); + lblCreatureCount.setVerticalAlignment(javax.swing.SwingConstants.TOP); + lblCreatureCount.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + lblCreatureCount.setFocusable(false); + lblCreatureCount.setInheritsPopupMenu(false); + lblCreatureCount.setRequestFocusEnabled(false); + lblCreatureCount.setVerifyInputWhenFocusTarget(false); + + chkPiles.setText("Piles"); + chkPiles.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + chkPiles.setMargin(new java.awt.Insets(3, 2, 2, 2)); + chkPiles.addActionListener(evt -> chkPilesActionPerformed(evt)); + + cbSortBy.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"SortBy"})); + cbSortBy.setToolTipText("Sort the cards if card view is active."); + cbSortBy.setMaximumSize(new java.awt.Dimension(120, 20)); + cbSortBy.setMinimumSize(new java.awt.Dimension(120, 20)); + cbSortBy.setName("SortBy"); // NOI18N + cbSortBy.setOpaque(false); + cbSortBy.setPreferredSize(new java.awt.Dimension(120, 20)); + cbSortBy.addActionListener(evt -> cbSortByActionPerformed(evt)); + + bgView.add(jToggleListView); + jToggleListView.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/list_panel.png"))); // NOI18N + jToggleListView.setToolTipText("Shows the cards as a list."); + jToggleListView.setBorder(null); + jToggleListView.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + jToggleListView.setMargin(new java.awt.Insets(2, 6, 2, 6)); + jToggleListView.setMaximumSize(new java.awt.Dimension(37, 25)); + jToggleListView.setMinimumSize(new java.awt.Dimension(37, 25)); + jToggleListView.setPreferredSize(new java.awt.Dimension(44, 22)); + jToggleListView.addActionListener(evt -> jToggleListViewActionPerformed(evt)); + + bgView.add(jToggleCardView); + jToggleCardView.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/card_panel.png"))); // NOI18N + jToggleCardView.setToolTipText("Shows the card as images."); + jToggleCardView.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + jToggleCardView.setMargin(new java.awt.Insets(2, 6, 2, 6)); + jToggleCardView.setPreferredSize(new java.awt.Dimension(40, 22)); + jToggleCardView.addActionListener(evt -> jToggleCardViewActionPerformed(evt)); + + javax.swing.GroupLayout panelControlLayout = new javax.swing.GroupLayout(panelControl); + panelControl.setLayout(panelControlLayout); + panelControlLayout.setHorizontalGroup( + panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelControlLayout.createSequentialGroup() + .addComponent(lblCount) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblLandCount) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblCreatureCount) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkPiles) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(cbSortBy, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jToggleListView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jToggleCardView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + panelControlLayout.setVerticalGroup( + panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelControlLayout.createSequentialGroup() + .addGroup(panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelControlLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblCount) + .addComponent(lblLandCount) + .addComponent(lblCreatureCount) + .addComponent(chkPiles)) + .addComponent(cbSortBy, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jToggleListView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jToggleCardView, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0)) + ); + + jToggleListView.getAccessibleContext().setAccessibleDescription("Switch between image and table view."); + + panelCardArea.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); + panelCardArea.setViewportView(cardArea); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panelControl, javax.swing.GroupLayout.PREFERRED_SIZE, 467, Short.MAX_VALUE) + .addComponent(panelCardArea) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(panelControl, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(2, 2, 2) + .addComponent(panelCardArea, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void jToggleCardViewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleCardViewActionPerformed + currentView = this; + panelCardArea.setViewportView(cardArea); + cbSortBy.setEnabled(true); + chkPiles.setEnabled(true); + PreferencesDialog.saveValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView"); + redrawCards(); + }//GEN-LAST:event_jToggleCardViewActionPerformed + + private void jToggleListViewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleListViewActionPerformed + currentView = mainModel; + panelCardArea.setViewportView(mainTable); + cbSortBy.setEnabled(false); + chkPiles.setEnabled(false); + PreferencesDialog.saveValue(PreferencesDialog.KEY_DRAFT_VIEW, "listView"); + redrawCards(); + }//GEN-LAST:event_jToggleListViewActionPerformed + + private void cbSortByActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbSortByActionPerformed + sortSetting.setSortBy((SortBy) cbSortBy.getSelectedItem()); + drawCards(sortSetting); + }//GEN-LAST:event_cbSortByActionPerformed + + private void chkPilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkPilesActionPerformed + sortSetting.setPilesToggle(chkPiles.isSelected()); + drawCards(sortSetting); + }//GEN-LAST:event_chkPilesActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.ButtonGroup bgView; + private javax.swing.JLayeredPane cardArea; + private javax.swing.JComboBox cbSortBy; + private javax.swing.JCheckBox chkPiles; + private javax.swing.JToggleButton jToggleCardView; + private javax.swing.JToggleButton jToggleListView; + private javax.swing.JLabel lblCount; + private javax.swing.JLabel lblCreatureCount; + private javax.swing.JLabel lblLandCount; + private javax.swing.JScrollPane panelCardArea; + private javax.swing.JPanel panelControl; + // End of variables declaration//GEN-END:variables + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + if (e.getClickCount() >= 1 && !e.isConsumed()) { + Object obj = e.getSource(); + if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0)) { // double clicks and repeated double clicks + e.consume(); + if (obj instanceof Card) { + if (e.isAltDown()) { + cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); + } else { + cardEventSource.fireEvent(((Card) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); + } + } else if (obj instanceof MageCard) { + if (e.isAltDown()) { + cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.ALT_DOUBLE_CLICK); + } else { + cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.DOUBLE_CLICK); + } + } + } + if (obj instanceof MageCard) { + checkMenu(e, ((MageCard) obj).getOriginal()); + } else { + checkMenu(e, null); + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (!e.isConsumed()) { + Object obj = e.getSource(); + if (obj instanceof MageCard) { + checkMenu(e, ((MageCard) obj).getOriginal()); + } else { + checkMenu(e, null); + } + } + } + + private void checkMenu(MouseEvent Me, SimpleCardView card) { + if (Me.isPopupTrigger()) { + Me.consume(); + cardEventSource.fireEvent(card, Me.getComponent(), Me.getX(), Me.getY(), ClientEventType.SHOW_POP_UP_MENU); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + public void setDisplayNoCopies(boolean value) { + mainModel.setDisplayNoCopies(value); + } + + @Override + public int cardsSize() { + return cards.size(); + } + + public void setSortBy(SortBy sortBy) { + if (sortBy != null) { + cbSortBy.setSelectedIndex(sortBy.ordinal()); + } + } + + public void setSortSetting(SortSetting sortSetting) { + this.sortSetting = sortSetting; + } + + } diff --git a/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java b/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java index 18c9980eb8..c3b6a3346e 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java @@ -10,6 +10,7 @@ package mage.client.cards; import mage.cards.CardDimensions; import mage.cards.MageCard; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.CardViewRarityComparator; import mage.client.util.ClientEventType; @@ -28,7 +29,6 @@ import java.util.ArrayList; import java.util.List; /** - * * @author BetaSteward_at_googlemail.com */ public class DraftGrid extends javax.swing.JPanel implements MouseListener { @@ -40,17 +40,19 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { protected MageCard markedCard; protected boolean emptyGrid; - /** Creates new form DraftGrid */ + /** + * Creates new form DraftGrid + */ public DraftGrid() { initComponents(); markedCard = null; - emptyGrid= true; + emptyGrid = true; } public void clear() { markedCard = null; this.clearCardEventListeners(); - for (Component comp: getComponents()) { + for (Component comp : getComponents()) { if (comp instanceof Card || comp instanceof MageCard) { this.remove(comp); } @@ -79,10 +81,10 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { CardDimensions cardDimension = null; int maxCards; - double scale ; + double scale; for (int i = 1; i < maxRows; i++) { - scale = (double) (this.getHeight()/i) / Constants.FRAME_MAX_HEIGHT; + scale = (double) (this.getHeight() / i) / Constants.FRAME_MAX_HEIGHT; cardDimension = new CardDimensions(scale); maxCards = this.getWidth() / (cardDimension.getFrameWidth() + offsetX); if ((maxCards * i) >= booster.size()) { @@ -100,8 +102,8 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { List sortedCards = new ArrayList<>(booster.values()); sortedCards.sort(new CardViewRarityComparator()); - for (CardView card: sortedCards) { - MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, dimension, null, true, true); + for (CardView card : sortedCards) { + MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, dimension, null, true, true, PreferencesDialog.getRenderMode()); cardImg.addMouseListener(this); add(cardImg); cardImg.update(card); @@ -133,7 +135,8 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { Plugins.instance.getActionCallback().mouseExited(null, null); } - /** This method is called from within the constructor to + /** + * This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. @@ -145,12 +148,12 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 400, Short.MAX_VALUE) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) ); layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 300, Short.MAX_VALUE) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -160,7 +163,7 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { if (e.getButton() == MouseEvent.BUTTON1) { Object obj = e.getSource(); if (obj instanceof MageCard) { - this.cardEventSource.fireEvent(((MageCard)obj).getOriginal(), ClientEventType.PICK_A_CARD); + this.cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.PICK_A_CARD); this.hidePopup(); AudioManager.playOnDraftSelect(); } @@ -177,8 +180,8 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { if (this.markedCard != null) { markedCard.setSelected(false); } - this.cardEventSource.fireEvent(((MageCard)obj).getOriginal(), ClientEventType.MARK_A_CARD); - markedCard = ((MageCard)obj); + this.cardEventSource.fireEvent(((MageCard) obj).getOriginal(), ClientEventType.MARK_A_CARD); + markedCard = ((MageCard) obj); markedCard.setSelected(true); repaint(); } 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 12ca7ef2f7..2733c0861f 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1,16 +1,5 @@ package mage.client.cards; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.swing.*; import mage.cards.Card; import mage.cards.MageCard; import mage.cards.decks.DeckCardInfo; @@ -31,6 +20,18 @@ import mage.view.CardsView; import org.apache.log4j.Logger; import org.mage.card.arcane.CardRenderer; +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + /** * Created by StravantUser on 2016-09-20. */ @@ -456,6 +457,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg --count; } } + private int count = 0; } @@ -511,14 +513,14 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg }; private final CardTypeCounter[] allCounters = { - creatureCounter, - landCounter, - artifactCounter, - enchantmentCounter, - instantCounter, - planeswalkerCounter, - sorceryCounter, - tribalCounter + creatureCounter, + landCounter, + artifactCounter, + enchantmentCounter, + instantCounter, + planeswalkerCounter, + sorceryCounter, + tribalCounter }; // Listener @@ -659,7 +661,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg @Override public String toString() { - return '(' + sort.toString() + ',' + Boolean.toString(separateCreatures) + ',' + Integer.toString(cardSize) + ')'; + return '(' + sort.toString() + ',' + separateCreatures + ',' + cardSize + ')'; } } @@ -1767,7 +1769,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg updateCounts(); // Create the card view - final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true); + final MageCard cardPanel = Plugins.instance.getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true, PreferencesDialog.getRenderMode()); cardPanel.update(card); cardPanel.setCardCaptionTopOffset(0); diff --git a/Mage.Client/src/main/java/mage/client/combat/CombatManager.java b/Mage.Client/src/main/java/mage/client/combat/CombatManager.java index b9be142a83..6c80cbd74a 100644 --- a/Mage.Client/src/main/java/mage/client/combat/CombatManager.java +++ b/Mage.Client/src/main/java/mage/client/combat/CombatManager.java @@ -3,8 +3,8 @@ package mage.client.combat; import mage.cards.MagePermanent; import mage.client.MageFrame; import mage.client.game.PlayAreaPanel; -import mage.client.util.audio.AudioManager; import mage.client.util.SettingsManager; +import mage.client.util.audio.AudioManager; import mage.client.util.gui.ArrowBuilder; import mage.view.CardView; import mage.view.CombatGroupView; @@ -67,7 +67,7 @@ public enum CombatManager { } private void drawAttacker(CombatGroupView group, CardView attacker, UUID gameId) { - for (PlayAreaPanel pa2 : MageFrame.getGame(gameId).getPlayers().values()) { + for (PlayAreaPanel pa2 : MageFrame.getGamePlayers(gameId).values()) { MagePermanent attackerCard = pa2.getBattlefieldPanel().getPermanents().get(attacker.getId()); if (attackerCard != null) { drawDefender(group, attackerCard, gameId); @@ -80,7 +80,7 @@ public enum CombatManager { UUID defenderId = group.getDefenderId(); if (defenderId != null) { parentPoint = getParentPoint(attackerCard); - PlayAreaPanel p = MageFrame.getGame(gameId).getPlayers().get(defenderId); + PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId); if (p != null) { Point target = p.getLocationOnScreen(); target.translate(-parentPoint.x, -parentPoint.y); @@ -88,7 +88,7 @@ public enum CombatManager { attackerPoint.translate(-parentPoint.x, -parentPoint.y); ArrowBuilder.getBuilder().addArrow(gameId, (int) attackerPoint.getX() + 45, (int) attackerPoint.getY() + 25, (int) target.getX() + 40, (int) target.getY() - 20, Color.red, ArrowBuilder.Type.COMBAT); } else { - for (PlayAreaPanel pa : MageFrame.getGame(gameId).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(gameId).values()) { MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(defenderId); if (permanent != null) { Point target = permanent.getLocationOnScreen(); @@ -104,7 +104,7 @@ public enum CombatManager { private void drawBlockers(CombatGroupView group, MagePermanent attackerCard, UUID gameId) { for (CardView blocker : group.getBlockers().values()) { - for (PlayAreaPanel pa : MageFrame.getGame(gameId).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(gameId).values()) { MagePermanent blockerCard = pa.getBattlefieldPanel().getPermanents().get(blocker.getId()); if (blockerCard != null) { parentPoint = getParentPoint(blockerCard); diff --git a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/ChoiceDialog.java b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/ChoiceDialog.java index 13594995e7..bb537f8b4b 100644 --- a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/ChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/ChoiceDialog.java @@ -8,6 +8,7 @@ import mage.client.components.ext.dlg.DialogContainer; import mage.client.components.ext.dlg.DialogManager; import mage.client.components.ext.dlg.DlgParams; import mage.client.components.ext.dlg.IDialogPanel; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Command; import mage.client.util.SettingsManager; @@ -162,7 +163,7 @@ public class ChoiceDialog extends IDialogPanel { } CardView card = cardList.get(i); - MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true); + MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true, PreferencesDialog.getRenderMode()); cardImg.setLocation(dx, dy + j * (height + 30)); add(cardImg); diff --git a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java index 8645e5f3de..db4624c769 100644 --- a/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java +++ b/Mage.Client/src/main/java/mage/client/components/ext/dlg/impl/StackDialog.java @@ -7,6 +7,7 @@ import mage.client.components.ext.dlg.DialogContainer; import mage.client.components.ext.dlg.DialogManager; import mage.client.components.ext.dlg.DlgParams; import mage.client.components.ext.dlg.IDialogPanel; +import mage.client.dialog.PreferencesDialog; import mage.client.game.FeedbackPanel; import mage.client.plugins.impl.Plugins; import mage.client.util.Command; @@ -33,14 +34,14 @@ public class StackDialog extends IDialogPanel { private JLayeredPane jLayeredPane; private final FeedbackPanel feedbackPanel; - + private final UUID gameId; private static class CustomLabel extends JLabel { @Override public void paintComponent(Graphics g) { - Graphics2D g2D = (Graphics2D)g; + Graphics2D g2D = (Graphics2D) g; g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -66,7 +67,7 @@ public class StackDialog extends IDialogPanel { /** * This method initializes this - * + * * @return void */ private void initialize() { @@ -111,7 +112,7 @@ public class StackDialog extends IDialogPanel { for (CardView card : cards.values()) { if (card instanceof StackAbilityView) { - CardView tmp = ((StackAbilityView)card).getSourceCard(); + CardView tmp = ((StackAbilityView) card).getSourceCard(); tmp.overrideRules(card.getRules()); tmp.setIsAbility(true); tmp.overrideTargets(card.getTargets()); @@ -119,7 +120,7 @@ public class StackDialog extends IDialogPanel { card = tmp; } - MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true); + MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, getCardDimension(), gameId, true, true, PreferencesDialog.getRenderMode()); //cardImg.setBorder(BorderFactory.createLineBorder(Color.red)); cardImg.setLocation(dx, dy); @@ -143,10 +144,11 @@ public class StackDialog extends IDialogPanel { jButtonAccept.setObserver(new Command() { @Override public void execute() { - DialogManager.getManager(gameId).fadeOut((DialogContainer)getParent()); + DialogManager.getManager(gameId).fadeOut((DialogContainer) getParent()); //GameManager.getInputControl().getInput().selectButtonOK(); StackDialog.this.feedbackPanel.doClick(); } + private static final long serialVersionUID = 1L; }); } @@ -166,11 +168,12 @@ public class StackDialog extends IDialogPanel { jButtonResponse.setObserver(new Command() { @Override public void execute() { - DialogManager.getManager(gameId).fadeOut((DialogContainer)getParent()); + DialogManager.getManager(gameId).fadeOut((DialogContainer) getParent()); } + private static final long serialVersionUID = 1L; }); } return jButtonResponse; } - } \ No newline at end of file +} \ No newline at end of file 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 c58ae335d5..cf77d9d09e 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 @@ -136,7 +136,7 @@ public class DeckGeneratorPool int cardCount = getCardCount((card.getName())); // No need to check if the land is valid for the colors chosen // They are all filtered before searching for lands to include in the deck. - return (cardCount < 4); + return (cardCount < (isSingleton ? 1 : 4)); } 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 357ecda0c6..05ccb0f917 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -447,7 +447,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } } }); - refreshDeck(); + refreshDeck(true); if (mode == DeckEditorMode.FREE_BUILDING) { setDropTarget(new DropTarget(this, new DnDDeckTargetListener() { @@ -1414,14 +1414,15 @@ class ImportFilter extends FileFilter { || ext.equalsIgnoreCase("txt") || ext.equalsIgnoreCase("dek") || ext.equalsIgnoreCase("cod") - || ext.equalsIgnoreCase("o8d"); + || ext.equalsIgnoreCase("o8d") + || ext.equalsIgnoreCase("draft"); } return false; } @Override public String getDescription() { - return "All formats (*.dec; *.mwDeck; *.txt; *.dek; *.cod; *.o8d)"; + return "All formats (*.dec; *.mwDeck; *.txt; *.dek; *.cod; *.o8d; *.draft)"; } } 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 80b4c18fbf..e7fea575ad 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 @@ -8,6 +8,7 @@ import mage.cards.repository.ExpansionRepository; import mage.client.MageFrame; import mage.client.cards.BigCard; import mage.client.components.HoverButton; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Config; import mage.client.util.ImageHelper; @@ -406,7 +407,7 @@ public class MageBook extends JComponent { if (cardDimension == null) { cardDimension = new Dimension(Config.dimensions.getFrameWidth(), Config.dimensions.getFrameHeight()); } - final MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true); + final MageCard cardImg = Plugins.instance.getMageCard(card, bigCard, cardDimension, gameId, true, true, PreferencesDialog.getRenderMode()); cardImg.setBounds(rectangle); jLayeredPane.add(cardImg, JLayeredPane.DEFAULT_LAYER, 10); cardImg.update(card); @@ -447,7 +448,7 @@ public class MageBook extends JComponent { newToken.removeSummoningSickness(); PermanentView theToken = new PermanentView(newToken, null, null, null); theToken.setInViewerOnly(true); - final MageCard cardImg = Plugins.instance.getMagePermanent(theToken, bigCard, cardDimension, gameId, true); + final MageCard cardImg = Plugins.instance.getMagePermanent(theToken, bigCard, cardDimension, gameId, true, PreferencesDialog.getRenderMode()); cardImg.setBounds(rectangle); jLayeredPane.add(cardImg, JLayeredPane.DEFAULT_LAYER, 10); cardImg.update(theToken); diff --git a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form index 3055978fc8..3fb27cbf15 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form @@ -23,26 +23,26 @@ - + - - + + - - - + + + - + @@ -60,10 +60,10 @@ - + + + - - @@ -94,17 +94,7 @@ - - - - - - - - - - - + @@ -115,5 +105,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java index c9b8c1165c..d93a3bf714 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java @@ -6,15 +6,20 @@ import mage.utils.MageVersion; import javax.swing.*; import java.awt.event.KeyEvent; - /** * @author JayDi85 */ public class AboutDialog extends MageDialog { + private static String devsList = "BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, " + + "Jeff, Plopman, dustinconrad, emerald000, fireshoes, lunaskyrise, " + + "mnapoleon, jgod, LoneFox, drmDev, spjspj, TheElk801, L_J, JayDi85, " + + "jmharmon, Ketsuban, hitch17"; + public AboutDialog() { initComponents(); this.modal = false; + panelDevs.setText(devsList + " and hundreds of other developers from https://github.com/magefree/mage/graphs/contributors"); } public void showDialog(MageVersion version) { @@ -54,9 +59,10 @@ public class AboutDialog extends MageDialog { jLabel1 = new javax.swing.JLabel(); lblVersion = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); - jLabel3 = new javax.swing.JLabel(); - jLabel4 = new javax.swing.JLabel(); btnWhatsNew = new javax.swing.JButton(); + scrollDevs = new javax.swing.JScrollPane(); + panelDevs = new javax.swing.JTextPane(); + labelDevs = new javax.swing.JLabel(); setMaximizable(true); setTitle("About XMage"); @@ -72,11 +78,7 @@ public class AboutDialog extends MageDialog { lblVersion.setText("0.0.0"); - jLabel2.setText("Courtesy: BetaSteward@googlemail.com. Site: http://XMage.de/"); - - jLabel3.setText("Devs: BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, Jeff, Plopman, dustinconrad, emerald000.,"); - - jLabel4.setText("fireshoes, lunaskyrise, mnapoleon, jgod, LoneFox."); + jLabel2.setText("Courtesy: BetaSteward@googlemail.com. Site: http://xmage.de/"); btnWhatsNew.setText("What's new"); btnWhatsNew.addActionListener(new java.awt.event.ActionListener() { @@ -85,27 +87,34 @@ public class AboutDialog extends MageDialog { } }); + scrollDevs.setBorder(null); + + panelDevs.setEditable(false); + scrollDevs.setViewportView(panelDevs); + + labelDevs.setText("Developers:"); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollDevs) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(btnWhatsNew, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(btnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(jLabel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lblVersion)) - .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING)) - .addGap(0, 0, Short.MAX_VALUE))) + .addComponent(jLabel2) + .addComponent(labelDevs)) + .addGap(0, 80, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( @@ -118,10 +127,10 @@ public class AboutDialog extends MageDialog { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelDevs) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(scrollDevs, javax.swing.GroupLayout.DEFAULT_SIZE, 161, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel4) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 68, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(btnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(btnWhatsNew, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE)) @@ -144,9 +153,10 @@ public class AboutDialog extends MageDialog { private javax.swing.JButton btnWhatsNew; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; - private javax.swing.JLabel jLabel4; + private javax.swing.JLabel labelDevs; private javax.swing.JLabel lblVersion; + private javax.swing.JTextPane panelDevs; + private javax.swing.JScrollPane scrollDevs; // End of variables declaration//GEN-END:variables } diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java index fefd157c06..1007492696 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -88,6 +88,9 @@ public class ConnectDialog extends MageDialog { MagePreferences.setUserName(serverAddress, txtUserName.getText().trim()); MagePreferences.setPassword(serverAddress, String.valueOf(txtPassword.getPassword()).trim()); MageFrame.getPreferences().put(KEY_CONNECT_AUTO_CONNECT, Boolean.toString(chkAutoConnect.isSelected())); + + // last settings for reconnect + MagePreferences.saveLastServer(); } /** @@ -517,7 +520,6 @@ public class ConnectDialog extends MageDialog { char[] input = new char[0]; try { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); connection = new Connection(); connection.setHost(this.txtServer.getText().trim()); connection.setPort(Integer.valueOf(this.txtPort.getText().trim())); @@ -545,6 +547,12 @@ public class ConnectDialog extends MageDialog { }//GEN-LAST:event_btnConnectActionPerformed + private void setConnectButtonsState(boolean enable) { + btnConnect.setEnabled(enable); + btnRegister.setEnabled(enable); + btnForgotPassword.setEnabled(enable); + } + private class ConnectTask extends SwingWorker { private boolean result = false; @@ -555,7 +563,7 @@ public class ConnectDialog extends MageDialog { @Override protected Boolean doInBackground() throws Exception { lblStatus.setText("Connecting..."); - btnConnect.setEnabled(false); + setConnectButtonsState(false); result = MageFrame.connect(connection); lastConnectError = SessionHandler.getLastConnectError(); return result; @@ -565,7 +573,6 @@ public class ConnectDialog extends MageDialog { protected void done() { try { get(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS); - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); if (result) { lblStatus.setText(""); connected(); @@ -578,13 +585,13 @@ public class ConnectDialog extends MageDialog { } catch (ExecutionException ex) { logger.fatal("Update Players Task error", ex); } catch (CancellationException ex) { - logger.info("Connect was canceled"); + logger.info("Connect: canceled"); lblStatus.setText("Connect was canceled"); } catch (TimeoutException ex) { logger.fatal("Connection timeout: ", ex); } finally { MageFrame.stopConnecting(); - btnConnect.setEnabled(true); + setConnectButtonsState(true); } } } diff --git a/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java b/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java index 7c932ef941..5abf39fff2 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java @@ -109,6 +109,7 @@ public class MageDialog extends javax.swing.JInternalFrame { SwingUtilities.invokeAndWait(() -> stopModal()); } catch (InterruptedException ex) { LOGGER.fatal("MageDialog error", ex); + Thread.currentThread().interrupt(); } catch (InvocationTargetException ex) { LOGGER.fatal("MageDialog error", ex); } @@ -184,9 +185,10 @@ public class MageDialog extends javax.swing.JInternalFrame { wait(); } } - } catch (InterruptedException ignored) { + } catch (InterruptedException e) { + LOGGER.fatal("MageDialog error", e); + Thread.currentThread().interrupt(); } - } private synchronized void stopModal() { diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index f433f0c0cb..1fece57129 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -637,13 +637,18 @@ public class NewTableDialog extends MageDialog { case "Variant Magic - Commander": case "Variant Magic - Duel Commander": case "Variant Magic - MTGO 1v1 Commander": - case "Variant Magic - Freeform Commander": case "Variant Magic - Penny Dreadful Commander": if (!options.getGameType().startsWith("Commander")) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Deck type Commander needs also a Commander game type", "Error", JOptionPane.ERROR_MESSAGE); return false; } break; + case "Variant Magic - Freeform Commander": + if (!options.getGameType().startsWith("Freeform Commander")) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Deck type Freeform Commander needs also a Freeform Commander game type", "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + break; case "Variant Magic - Brawl": case "Variant Magic - Duel Brawl": if (!options.getGameType().startsWith("Brawl")) { @@ -678,6 +683,13 @@ public class NewTableDialog extends MageDialog { return false; } break; + case "Freeform Commander Two Player Duel": + case "Freeform Commander Free For All": + if (!options.getDeckType().equals("Variant Magic - Freeform Commander")) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Deck type Freeform Commander needs also a Freeform Commander game type", "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + break; case "Brawl Two Player Duel": case "Brawl Free For All": if (!options.getDeckType().equals("Variant Magic - Brawl") diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index ebe2c3239b..ffc4c952ed 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -31,6 +31,7 @@ import java.awt.*; import java.io.File; import java.util.List; import java.util.*; +import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com, JayDi85 @@ -655,7 +656,7 @@ public class NewTournamentDialog extends MageDialog { }// //GEN-END:initComponents private void cbTournamentTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTournamentTypeActionPerformed - setTournamentOptions((Integer) this.spnNumPlayers.getValue()); + prepareTourneyView((Integer) this.spnNumPlayers.getValue()); }//GEN-LAST:event_cbTournamentTypeActionPerformed private void btnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOkActionPerformed @@ -663,6 +664,15 @@ public class NewTournamentDialog extends MageDialog { // get settings TournamentOptions tOptions = getTournamentOptions(); + // CHECKS + TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); + if (tournamentType.isRandom() || tournamentType.isRichMan()) { + if (tOptions.getLimitedOptions().getSetCodes().isEmpty()) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Warning, you must select packs for the pool", "Warning", JOptionPane.WARNING_MESSAGE); + return; + } + } + // save last settings onSaveSettings(0, tOptions); @@ -837,19 +847,25 @@ public class NewTournamentDialog extends MageDialog { createPlayers((Integer) spnNumPlayers.getValue() - 1); } - private void setTournamentOptions(int numPlayers) { + private void prepareTourneyView(int numPlayers) { TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); activatePanelElements(tournamentType); + // players if (numPlayers < tournamentType.getMinPlayers() || numPlayers > tournamentType.getMaxPlayers()) { numPlayers = tournamentType.getMinPlayers(); - createPlayers(numPlayers - 1); + createPlayers(numPlayers - 1); // ? } this.spnNumPlayers.setModel(new SpinnerNumberModel(numPlayers, tournamentType.getMinPlayers(), tournamentType.getMaxPlayers(), 1)); this.spnNumPlayers.setEnabled(tournamentType.getMinPlayers() != tournamentType.getMaxPlayers()); createPlayers((Integer) spnNumPlayers.getValue() - 1); this.spnNumSeats.setModel(new SpinnerNumberModel(2, 2, tournamentType.getMaxPlayers(), 1)); + // packs + preparePacksView(tournamentType); + } + + private void preparePacksView(TournamentTypeView tournamentType) { if (tournamentType.isLimited()) { this.isRandom = tournamentType.isRandom(); this.isRichMan = tournamentType.isRichMan(); @@ -859,7 +875,6 @@ public class NewTournamentDialog extends MageDialog { createPacks(tournamentType.getNumBoosters()); } } - } private void setNumberOfSwissRoundsMin(int numPlayers) { @@ -921,6 +936,43 @@ public class NewTournamentDialog extends MageDialog { } } + private String prepareVersionStr(int version, boolean saveMode) { + // version: 0, 1, 2... to save/load + // version: -1 to reset (load from empty record) + switch (version) { + case -1: + return saveMode ? "" : "-1"; // can't save to -1 version + case 1: + return "1"; + case 2: + return "2"; + default: + return ""; + } + } + + private void loadRandomPacks(int version) { + String versionStr = prepareVersionStr(version, false); + ArrayList packList; + String packNames; + String randomPrefs = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT + versionStr, ""); + if (!randomPrefs.isEmpty()) { + packList = new ArrayList<>(Arrays.asList(randomPrefs.split(";"))); + packNames = randomPrefs; + } else { + ExpansionInfo[] allExpansions = ExpansionRepository.instance.getWithBoostersSortedByReleaseDate(); + packList = Arrays.stream(allExpansions).map(ExpansionInfo::getCode).collect(Collectors.toCollection(ArrayList::new)); + packNames = Arrays.stream(allExpansions).map(ExpansionInfo::getCode).collect(Collectors.joining(";")); + } + randomPackSelector.setSelectedPacks(packList); + txtRandomPacks.setText(packNames); + + // workaround to apply field's auto-size + this.pack(); + this.revalidate(); + this.repaint(); + } + private void createRandomPacks() { if (pnlRandomPacks.getComponentCount() == 0) { if (randomPackSelector == null) { @@ -930,20 +982,9 @@ public class NewTournamentDialog extends MageDialog { txtRandomPacks = new JTextArea(); txtRandomPacks.setEnabled(false); txtRandomPacks.setLineWrap(true); - String randomPrefs = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT, ""); - if (!randomPrefs.isEmpty()) { - txtRandomPacks.setText(randomPrefs); - ArrayList theList = new ArrayList<>(Arrays.asList(randomPrefs.split(";"))); - randomPackSelector.setSelectedPacks(theList); - } else { - ExpansionInfo[] allExpansions = ExpansionRepository.instance.getWithBoostersSortedByReleaseDate(); - StringBuilder packList = new StringBuilder(); - for (ExpansionInfo exp : allExpansions) { - packList.append(exp.getCode()); - packList.append(';'); - } - txtRandomPacks.setText(packList.toString()); - } + + loadRandomPacks(-1); // load default packs + txtRandomPacks.setAlignmentX(Component.LEFT_ALIGNMENT); pnlRandomPacks.add(txtRandomPacks); JButton btnSelectRandomPacks = new JButton(); @@ -953,6 +994,7 @@ public class NewTournamentDialog extends MageDialog { btnSelectRandomPacks.addActionListener(evt -> showRandomPackSelectorDialog()); pnlRandomPacks.add(btnSelectRandomPacks); } + txtRandomPacks.setText(txtRandomPacks.getText()); // workaround to apply field's auto-size this.pack(); this.revalidate(); this.repaint(); @@ -961,12 +1003,7 @@ public class NewTournamentDialog extends MageDialog { private void showRandomPackSelectorDialog() { randomPackSelector.setType(isRandom, isRichMan); randomPackSelector.showDialog(); - StringBuilder packList = new StringBuilder(); - for (String str : randomPackSelector.getSelectedPacks()) { - packList.append(str); - packList.append(';'); - } - this.txtRandomPacks.setText(packList.toString()); + this.txtRandomPacks.setText(String.join(";", randomPackSelector.getSelectedPacks())); this.pack(); this.revalidate(); this.repaint(); @@ -1169,6 +1206,7 @@ public class NewTournamentDialog extends MageDialog { if (tournamentType.isLimited()) { tOptions.getLimitedOptions().setConstructionTime((Integer) this.spnConstructTime.getValue() * 60); tOptions.getLimitedOptions().setIsRandom(tournamentType.isRandom()); + tOptions.getLimitedOptions().setIsRichMan(tournamentType.isRichMan()); if (tournamentType.isCubeBooster()) { tOptions.getLimitedOptions().setDraftCubeName(this.cbDraftCube.getSelectedItem().toString()); if (!(cubeFromDeckFilename.isEmpty())) { @@ -1188,6 +1226,7 @@ public class NewTournamentDialog extends MageDialog { this.isRichMan = tournamentType.isRichMan(); tOptions.getLimitedOptions().getSetCodes().clear(); ArrayList selected = randomPackSelector.getSelectedPacks(); + Collections.shuffle(selected); int maxPacks = 3 * (players.size() + 1); if (tournamentType.isRichMan()) { maxPacks = 36; @@ -1197,7 +1236,6 @@ public class NewTournamentDialog extends MageDialog { infoString.append(maxPacks); infoString.append(" sets will be randomly chosen."); JOptionPane.showMessageDialog(MageFrame.getDesktop(), infoString, "Information", JOptionPane.INFORMATION_MESSAGE); - Collections.shuffle(selected); tOptions.getLimitedOptions().getSetCodes().addAll(selected.subList(0, maxPacks)); } else { tOptions.getLimitedOptions().getSetCodes().addAll(selected); @@ -1242,23 +1280,7 @@ public class NewTournamentDialog extends MageDialog { } private void onLoadSettings(int version) { - - String versionStr = ""; - switch (version) { - case -1: - versionStr = "-1"; // default (empty) - break; - case 1: - versionStr = "1"; - break; - case 2: - versionStr = "2"; - break; - default: - versionStr = ""; - break; - } - + String versionStr = prepareVersionStr(version, false); int numPlayers; txtName.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NAME + versionStr, "Tournament")); txtPassword.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PASSWORD + versionStr, "")); @@ -1298,16 +1320,13 @@ public class NewTournamentDialog extends MageDialog { activatePanelElements(tournamentType); if (tournamentType.isLimited()) { - if (!tournamentType.isDraft()) { - numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_SEALED + versionStr, "2")); - setTournamentOptions(numPlayers); - loadBoosterPacks(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_SEALED + versionStr, "")); - } - if (tournamentType.isDraft()) { numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_DRAFT + versionStr, "4")); - setTournamentOptions(numPlayers); - if (!(tournamentType.isRandom() || tournamentType.isRichMan())) { + prepareTourneyView(numPlayers); + + if (tournamentType.isRandom() || tournamentType.isRichMan()) { + loadRandomPacks(version); + } else { loadBoosterPacks(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_DRAFT + versionStr, "")); } @@ -1318,6 +1337,10 @@ public class NewTournamentDialog extends MageDialog { break; } } + } else { + numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_SEALED + versionStr, "2")); + prepareTourneyView(numPlayers); + loadBoosterPacks(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_SEALED + versionStr, "")); } } this.cbAllowSpectators.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS + versionStr, "Yes").equals("Yes")); @@ -1332,20 +1355,7 @@ public class NewTournamentDialog extends MageDialog { } private void onSaveSettings(int version, TournamentOptions tOptions) { - - String versionStr = ""; - switch (version) { - case 1: - versionStr = "1"; - break; - case 2: - versionStr = "2"; - break; - default: - versionStr = ""; - break; - } - + String versionStr = prepareVersionStr(version, true); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NAME + versionStr, tOptions.getName()); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PASSWORD + versionStr, tOptions.getPassword()); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_TIME_LIMIT + versionStr, Integer.toString(tOptions.getMatchOptions().getPriorityTime())); @@ -1375,15 +1385,8 @@ public class NewTournamentDialog extends MageDialog { if (deckFile != null && !deckFile.isEmpty()) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE + versionStr, deckFile); } - - if (tOptions.getLimitedOptions().getIsRandom()) { - // save random boosters to prefs - StringBuilder packlist = new StringBuilder(); - for (String pack : this.randomPackSelector.getSelectedPacks()) { - packlist.append(pack); - packlist.append(';'); - } - PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT + versionStr, packlist.toString()); + if (tOptions.getLimitedOptions().getIsRandom() || tOptions.getLimitedOptions().getIsRichMan()) { + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT + versionStr, String.join(";", this.randomPackSelector.getSelectedPacks())); } } PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS + versionStr, (tOptions.isWatchingAllowed() ? "Yes" : "No")); diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index f51d3fb355..9d29eef90f 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -24,7 +24,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -4071,7 +4071,7 @@ - + @@ -4296,12 +4296,12 @@ - + - + @@ -4316,7 +4316,7 @@ - + @@ -4341,6 +4341,7 @@ + @@ -4348,34 +4349,24 @@ - - - - + + + + + - - - - - - - - - - - - - - - - + + + + + + - - + @@ -4385,25 +4376,23 @@ - - + - - - + - + + - + - + @@ -4431,14 +4420,6 @@ - - - - - - - - @@ -4465,7 +4446,7 @@ - + @@ -4487,145 +4468,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -4696,6 +4541,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4891,7 +4851,7 @@ - + @@ -5802,7 +5762,7 @@ - + @@ -5811,7 +5771,7 @@ - + @@ -6068,7 +6028,7 @@ - + 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 c869f6e827..0f26b4f9da 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -78,7 +78,6 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_CARD_IMAGES_USE_DEFAULT = "cardImagesUseDefault"; public static final String KEY_CARD_IMAGES_PATH = "cardImagesPath"; public static final String KEY_CARD_IMAGES_THREADS = "cardImagesThreads"; - public static final String KEY_CARD_IMAGES_CHECK = "cardImagesCheck"; public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip"; public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferedImageLaguage"; @@ -468,12 +467,16 @@ public class PreferencesDialog extends javax.swing.JDialog { cbUseDefaultImageFolder = new javax.swing.JCheckBox(); txtImageFolderPath = new javax.swing.JTextField(); btnBrowseImageLocation = new javax.swing.JButton(); - cbCheckForNewImages = new javax.swing.JCheckBox(); cbSaveToZipFiles = new javax.swing.JCheckBox(); cbPreferedImageLanguage = new javax.swing.JComboBox<>(); labelPreferedImageLanguage = new javax.swing.JLabel(); labelNumberOfDownloadThreads = new javax.swing.JLabel(); cbNumberOfDownloadThreads = new javax.swing.JComboBox(); + labelHint1 = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); + cbCardRenderImageFallback = new javax.swing.JCheckBox(); + cbCardRenderShowReminderText = new javax.swing.JCheckBox(); + cbCardRenderHideSetSymbol = new javax.swing.JCheckBox(); panelBackgroundImages = new javax.swing.JPanel(); cbUseDefaultBackground = new javax.swing.JCheckBox(); txtBackgroundImagePath = new javax.swing.JTextField(); @@ -482,12 +485,6 @@ public class PreferencesDialog extends javax.swing.JDialog { btnBrowseBattlefieldImage = new javax.swing.JButton(); cbUseDefaultBattleImage = new javax.swing.JCheckBox(); cbUseRandomBattleImage = new javax.swing.JCheckBox(); - jLabel14 = new javax.swing.JLabel(); - jLabel15 = new javax.swing.JLabel(); - jPanel1 = new javax.swing.JPanel(); - cbCardRenderImageFallback = new javax.swing.JCheckBox(); - cbCardRenderShowReminderText = new javax.swing.JCheckBox(); - cbCardRenderHideSetSymbol = new javax.swing.JCheckBox(); tabSounds = new javax.swing.JPanel(); sounds_clips = new javax.swing.JPanel(); cbEnableGameSounds = new javax.swing.JCheckBox(); @@ -872,7 +869,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(main_gamelog, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(main_battlefield, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(39, Short.MAX_VALUE)) ); main_card.getAccessibleContext().setAccessibleName("Game panel"); @@ -1535,7 +1532,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(checkBoxEndTurnOthers)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(phases_stopSettings, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(160, Short.MAX_VALUE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); tabsPanel.addTab("Phases & Priority", tabPhases); @@ -1558,13 +1555,6 @@ public class PreferencesDialog extends javax.swing.JDialog { } }); - cbCheckForNewImages.setText("Check for new images on startup"); - cbCheckForNewImages.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cbCheckForNewImagesActionPerformed(evt); - } - }); - cbSaveToZipFiles.setText("Store images in zip files"); cbSaveToZipFiles.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -1575,7 +1565,7 @@ public class PreferencesDialog extends javax.swing.JDialog { cbPreferedImageLanguage.setMaximumRowCount(20); cbPreferedImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{"Item 1", "Item 2", "Item 3", "Item 4"})); - labelPreferedImageLanguage.setText("Prefered image language:"); + labelPreferedImageLanguage.setText("Default images language:"); labelPreferedImageLanguage.setFocusable(false); labelNumberOfDownloadThreads.setText("Number of download threads:"); @@ -1583,6 +1573,8 @@ public class PreferencesDialog extends javax.swing.JDialog { cbNumberOfDownloadThreads.setMaximumRowCount(20); cbNumberOfDownloadThreads.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"Item 1", "Item 2", "Item 3", "Item 4"})); + labelHint1.setText("(change it to 1-3 if image source bans your IP for too many connections)"); + org.jdesktop.layout.GroupLayout panelCardImagesLayout = new org.jdesktop.layout.GroupLayout(panelCardImages); panelCardImages.setLayout(panelCardImagesLayout); panelCardImagesLayout.setHorizontalGroup( @@ -1590,158 +1582,46 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(panelCardImagesLayout.createSequentialGroup() .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(panelCardImagesLayout.createSequentialGroup() - .addContainerGap() + .add(cbUseDefaultImageFolder) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(txtImageFolderPath) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(btnBrowseImageLocation)) .add(panelCardImagesLayout.createSequentialGroup() .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false) - .add(panelCardImagesLayout.createSequentialGroup() - .add(cbCheckForNewImages) - .add(147, 147, 147)) - .add(org.jdesktop.layout.GroupLayout.LEADING, panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) + .add(cbSaveToZipFiles) + .add(panelCardImagesLayout.createSequentialGroup() + .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(labelNumberOfDownloadThreads) + .add(labelPreferedImageLanguage)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(panelCardImagesLayout.createSequentialGroup() - .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false) - .add(org.jdesktop.layout.GroupLayout.LEADING, panelCardImagesLayout.createSequentialGroup() - .addContainerGap() - .add(labelPreferedImageLanguage)) - .add(org.jdesktop.layout.GroupLayout.LEADING, cbSaveToZipFiles)) - .add(20, 20, 20) - .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) - .add(panelCardImagesLayout.createSequentialGroup() - .addContainerGap() - .add(labelNumberOfDownloadThreads) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))) - .add(cbUseDefaultImageFolder)) - .add(0, 391, Short.MAX_VALUE))) + .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) + .add(labelHint1))))) + .add(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); panelCardImagesLayout.setVerticalGroup( panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(panelCardImagesLayout.createSequentialGroup() - .add(cbUseDefaultImageFolder) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(cbUseDefaultImageFolder) .add(txtImageFolderPath, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(btnBrowseImageLocation)) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(cbCheckForNewImages) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(cbSaveToZipFiles) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(labelNumberOfDownloadThreads) - .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(labelHint1)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) - .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .add(labelPreferedImageLanguage))) - ); - - panelBackgroundImages.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Background images")); - - cbUseDefaultBackground.setText("Use default image"); - cbUseDefaultBackground.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cbUseDefaultBackgroundActionPerformed(evt); - } - }); - - txtBackgroundImagePath.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - txtBackgroundImagePathActionPerformed(evt); - } - }); - - btnBrowseBackgroundImage.setText("Browse..."); - btnBrowseBackgroundImage.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnBrowseBackgroundImageActionPerformed(evt); - } - }); - - txtBattlefieldImagePath.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - txtBattlefieldImagePathActionPerformed(evt); - } - }); - - btnBrowseBattlefieldImage.setText("Browse..."); - btnBrowseBattlefieldImage.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnBrowseBattlefieldImageActionPerformed(evt); - } - }); - - cbUseDefaultBattleImage.setText("Use default battlefield image"); - cbUseDefaultBattleImage.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cbUseDefaultBattleImageActionPerformed(evt); - } - }); - - cbUseRandomBattleImage.setText("Select random battlefield image"); - cbUseRandomBattleImage.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cbUseRandomBattleImageActionPerformed(evt); - } - }); - - jLabel14.setText("Background:"); - - jLabel15.setText("Battlefield:"); - - org.jdesktop.layout.GroupLayout panelBackgroundImagesLayout = new org.jdesktop.layout.GroupLayout(panelBackgroundImages); - panelBackgroundImages.setLayout(panelBackgroundImagesLayout); - panelBackgroundImagesLayout.setHorizontalGroup( - panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .add(19, 19, 19) - .add(jLabel14)) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .add(25, 25, 25) - .add(jLabel15))) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(txtBackgroundImagePath) - .add(txtBattlefieldImagePath)) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(org.jdesktop.layout.GroupLayout.TRAILING, btnBrowseBackgroundImage) - .add(org.jdesktop.layout.GroupLayout.TRAILING, btnBrowseBattlefieldImage))) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(cbUseRandomBattleImage) - .add(cbUseDefaultBattleImage) - .add(cbUseDefaultBackground)) - .add(0, 0, Short.MAX_VALUE))) - .addContainerGap()) - ); - panelBackgroundImagesLayout.setVerticalGroup( - panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(panelBackgroundImagesLayout.createSequentialGroup() - .addContainerGap() - .add(cbUseDefaultBackground) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) - .add(txtBackgroundImagePath, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .add(btnBrowseBackgroundImage) - .add(jLabel14)) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(cbUseDefaultBattleImage) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(cbUseRandomBattleImage) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) - .add(txtBattlefieldImagePath, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .add(btnBrowseBattlefieldImage) - .add(jLabel15))) + .add(labelPreferedImageLanguage) + .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) ); jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)")); @@ -1789,6 +1669,95 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(0, 0, Short.MAX_VALUE)) ); + panelBackgroundImages.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Background images")); + + cbUseDefaultBackground.setText("Use default location for backgrounds"); + cbUseDefaultBackground.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbUseDefaultBackgroundActionPerformed(evt); + } + }); + + txtBackgroundImagePath.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtBackgroundImagePathActionPerformed(evt); + } + }); + + btnBrowseBackgroundImage.setText("Browse..."); + btnBrowseBackgroundImage.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnBrowseBackgroundImageActionPerformed(evt); + } + }); + + txtBattlefieldImagePath.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtBattlefieldImagePathActionPerformed(evt); + } + }); + + btnBrowseBattlefieldImage.setText("Browse..."); + btnBrowseBattlefieldImage.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnBrowseBattlefieldImageActionPerformed(evt); + } + }); + + cbUseDefaultBattleImage.setText("Use default battlefield image"); + cbUseDefaultBattleImage.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbUseDefaultBattleImageActionPerformed(evt); + } + }); + + cbUseRandomBattleImage.setText("Use random background"); + cbUseRandomBattleImage.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbUseRandomBattleImageActionPerformed(evt); + } + }); + + org.jdesktop.layout.GroupLayout panelBackgroundImagesLayout = new org.jdesktop.layout.GroupLayout(panelBackgroundImages); + panelBackgroundImages.setLayout(panelBackgroundImagesLayout); + panelBackgroundImagesLayout.setHorizontalGroup( + panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(panelBackgroundImagesLayout.createSequentialGroup() + .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(panelBackgroundImagesLayout.createSequentialGroup() + .add(cbUseDefaultBackground) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) + .add(txtBackgroundImagePath) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(btnBrowseBackgroundImage)) + .add(panelBackgroundImagesLayout.createSequentialGroup() + .add(cbUseRandomBattleImage) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) + .add(txtBattlefieldImagePath) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(btnBrowseBattlefieldImage)) + .add(panelBackgroundImagesLayout.createSequentialGroup() + .add(cbUseDefaultBattleImage) + .add(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + panelBackgroundImagesLayout.setVerticalGroup( + panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(panelBackgroundImagesLayout.createSequentialGroup() + .add(cbUseDefaultBattleImage) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(cbUseDefaultBackground) + .add(txtBackgroundImagePath, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(btnBrowseBackgroundImage)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(panelBackgroundImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(cbUseRandomBattleImage) + .add(txtBattlefieldImagePath, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(btnBrowseBattlefieldImage)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + org.jdesktop.layout.GroupLayout tabImagesLayout = new org.jdesktop.layout.GroupLayout(tabImages); tabImages.setLayout(tabImagesLayout); tabImagesLayout.setHorizontalGroup( @@ -1810,7 +1779,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(panelBackgroundImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(133, Short.MAX_VALUE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); tabsPanel.addTab("Images", tabImages); @@ -2385,7 +2354,7 @@ public class PreferencesDialog extends javax.swing.JDialog { tabAvatarsLayout.setVerticalGroup( tabAvatarsLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(tabAvatarsLayout.createSequentialGroup() - .add(avatarPane, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 620, Short.MAX_VALUE) + .add(avatarPane, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addContainerGap()) ); @@ -2420,14 +2389,14 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(connection_serversLayout.createSequentialGroup() .add(141, 141, 141) .add(jLabel17))) - .addContainerGap(251, Short.MAX_VALUE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); connection_serversLayout.setVerticalGroup( connection_serversLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(connection_serversLayout.createSequentialGroup() .add(connection_serversLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false) .add(lblURLServerList, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(txtURLServerList, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) + .add(txtURLServerList, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(jLabel17)) ); @@ -2663,7 +2632,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(keyToggleRecordMacro, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 100, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(keySwitchChat, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 100, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(controlsDescriptionLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 481, Short.MAX_VALUE) + .add(controlsDescriptionLabel) .addContainerGap()) ); tabControlsLayout.setVerticalGroup( @@ -2749,7 +2718,7 @@ public class PreferencesDialog extends javax.swing.JDialog { getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() + .add(layout.createSequentialGroup() .addContainerGap() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(org.jdesktop.layout.GroupLayout.LEADING, tabsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) @@ -2763,7 +2732,7 @@ public class PreferencesDialog extends javax.swing.JDialog { layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() - .add(tabsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(tabsPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 554, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(saveButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 30, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) @@ -2892,7 +2861,6 @@ public class PreferencesDialog extends javax.swing.JDialog { // images save(prefs, dialog.cbUseDefaultImageFolder, KEY_CARD_IMAGES_USE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY); saveImagesPath(prefs); - save(prefs, dialog.cbCheckForNewImages, KEY_CARD_IMAGES_CHECK, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbNumberOfDownloadThreads, KEY_CARD_IMAGES_THREADS); save(prefs, dialog.cbPreferedImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE); @@ -3198,31 +3166,6 @@ public class PreferencesDialog extends javax.swing.JDialog { // TODO add your handling code here: }//GEN-LAST:event_cbCardRenderShowReminderTextActionPerformed - private void cbSaveToZipFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbSaveToZipFilesActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_cbSaveToZipFilesActionPerformed - - private void cbCheckForNewImagesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbCheckForNewImagesActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_cbCheckForNewImagesActionPerformed - - private void btnBrowseImageLocationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseImageLocationActionPerformed - int returnVal = fc.showOpenDialog(PreferencesDialog.this); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - txtImageFolderPath.setText(file.getAbsolutePath()); - } - }//GEN-LAST:event_btnBrowseImageLocationActionPerformed - - private void cbUseDefaultImageFolderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseDefaultImageFolderActionPerformed - if (cbUseDefaultImageFolder.isSelected()) { - useDefaultPath(); - } else { - useConfigurablePath(); - } - }//GEN-LAST:event_cbUseDefaultImageFolderActionPerformed - private void cbCardRenderHideSetSymbolActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbCardRenderHideSetSymbolActionPerformed // TODO add your handling code here: }//GEN-LAST:event_cbCardRenderHideSetSymbolActionPerformed @@ -3263,6 +3206,27 @@ public class PreferencesDialog extends javax.swing.JDialog { // TODO add your handling code here: }//GEN-LAST:event_cbStopBlockWithZeroActionPerformed + private void cbSaveToZipFilesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbSaveToZipFilesActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cbSaveToZipFilesActionPerformed + + private void btnBrowseImageLocationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseImageLocationActionPerformed + int returnVal = fc.showOpenDialog(PreferencesDialog.this); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + txtImageFolderPath.setText(file.getAbsolutePath()); + } + }//GEN-LAST:event_btnBrowseImageLocationActionPerformed + + private void cbUseDefaultImageFolderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseDefaultImageFolderActionPerformed + if (cbUseDefaultImageFolder.isSelected()) { + useDefaultPath(); + } else { + useConfigurablePath(); + } + }//GEN-LAST:event_cbUseDefaultImageFolderActionPerformed + private void showProxySettings() { Connection.ProxyType proxyType = (Connection.ProxyType) cbProxyType.getSelectedItem(); switch (proxyType) { @@ -3446,7 +3410,6 @@ public class PreferencesDialog extends javax.swing.JDialog { dialog.txtImageFolderPath.setText(path); updateCache(KEY_CARD_IMAGES_PATH, path); } - load(prefs, dialog.cbCheckForNewImages, KEY_CARD_IMAGES_CHECK, "true"); load(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true"); dialog.cbNumberOfDownloadThreads.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_THREADS, "10")); dialog.cbPreferedImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); @@ -3751,6 +3714,14 @@ public class PreferencesDialog extends javax.swing.JDialog { } } + public static int getRenderMode() { + if (getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_FALLBACK, "false").equals("false")) { + return 0; // mtgo + } else { + return 1; // image + } + } + private static int getDefaultControlMofier(String key) { switch (key) { default: @@ -3964,7 +3935,6 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JCheckBox cbCardRenderHideSetSymbol; private javax.swing.JCheckBox cbCardRenderImageFallback; private javax.swing.JCheckBox cbCardRenderShowReminderText; - private javax.swing.JCheckBox cbCheckForNewImages; private javax.swing.JCheckBox cbConfirmEmptyManaPool; private javax.swing.JCheckBox cbDraftLogAutoSave; private javax.swing.JCheckBox cbEnableBattlefieldBGM; @@ -4014,8 +3984,6 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JPanel guiSizeBasic; private javax.swing.JPanel guiSizeGame; private javax.swing.JLabel jLabel11; - private javax.swing.JLabel jLabel14; - private javax.swing.JLabel jLabel15; private javax.swing.JLabel jLabel16; private javax.swing.JLabel jLabel17; private javax.swing.JLabel jLabelBeforeCombat; @@ -4076,6 +4044,7 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JLabel labelEndStep; private javax.swing.JLabel labelEnlargedImageSize; private javax.swing.JLabel labelGameFeedback; + private javax.swing.JLabel labelHint1; private javax.swing.JLabel labelMainStep; private javax.swing.JLabel labelNextTurn; private javax.swing.JLabel labelNumberOfDownloadThreads; diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.form b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.form new file mode 100644 index 0000000000..db55a2d6c5 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.form @@ -0,0 +1,120 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java new file mode 100644 index 0000000000..29b37987a6 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -0,0 +1,336 @@ +package mage.client.dialog; + +import mage.cards.Card; +import mage.cards.CardGraphicInfo; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.decks.Deck; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.cards.repository.ExpansionInfo; +import mage.cards.repository.ExpansionRepository; +import mage.client.MageFrame; +import mage.client.cards.BigCard; +import mage.client.util.GUISizeHelper; +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.game.GameImpl; +import mage.game.command.Emblem; +import mage.game.command.Plane; +import mage.game.command.emblems.AjaniAdversaryOfTyrantsEmblem; +import mage.game.command.planes.AkoumPlane; +import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; +import mage.game.mulligan.VancouverMulligan; +import mage.game.permanent.PermanentCard; +import mage.players.Player; +import mage.players.StubPlayer; +import mage.view.*; +import org.apache.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author JayDi85 + */ +public class TestCardRenderDialog extends MageDialog { + + private static final Logger logger = Logger.getLogger(TestCardRenderDialog.class); + float cardSizeMod = 1.0f; + + public TestCardRenderDialog() { + initComponents(); + } + + public void showDialog() { + this.setModal(false); + getRootPane().setDefaultButton(buttonCancel); + reloadCards(); + + // windows settings + MageFrame.getDesktop().remove(this); + if (this.isModal()) { + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + } else { + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + this.makeWindowCentered(); + + // Close on "ESC" + registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + this.setVisible(true); + } + + private void onCancel() { + this.removeDialog(); + } + + private PermanentView createCard(Game game, UUID controllerId, String code, String cardNumber, int power, int toughness, int damage) { + CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber); + ExpansionInfo setInfo = ExpansionRepository.instance.getSetByCode(code); + CardSetInfo testSet = new CardSetInfo(cardInfo.getName(), setInfo.getCode(), cardNumber, cardInfo.getRarity(), + new CardGraphicInfo(cardInfo.getFrameStyle(), cardInfo.usesVariousArt())); + Card card = CardImpl.createCard(cardInfo.getClassName(), testSet); + + Set cardsList = new HashSet<>(); + cardsList.add(card); + game.loadCards(cardsList, controllerId); + + PermanentCard perm = new PermanentCard(card, controllerId, game); + if (damage > 0) perm.damage(damage, controllerId, game); + if (power > 0) perm.getPower().setValue(power); + if (toughness > 0) perm.getToughness().setValue(toughness); + perm.removeSummoningSickness(); + if (perm.isTransformable()) { + perm.setTransformed(true); + } + PermanentView cardView = new PermanentView(perm, card, controllerId, game); + cardView.setInViewerOnly(true); + + return cardView; + } + + private AbilityView createEmblem(Emblem emblem) { + AbilityView emblemView = new AbilityView(emblem.getAbilities().get(0), emblem.getName(), new CardView(new EmblemView(emblem))); + emblemView.setName(emblem.getName()); + return emblemView; + } + + private AbilityView createPlane(Plane plane) { + AbilityView planeView = new AbilityView(plane.getAbilities().get(0), plane.getName(), new CardView(new PlaneView(plane))); + planeView.setName(plane.getName()); + return planeView; + } + + private void reloadCards() { + cardsPanel.cleanUp(); + cardsPanel.setCustomRenderMode(comboRenderMode.getSelectedIndex()); + + Game game = new TestGame(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); + Player player = new StubPlayer("player1", RangeOfInfluence.ALL); + Deck deck = new Deck(); + game.addPlayer(player, deck); + + BigCard big = new BigCard(); + CardsView view = new CardsView(); + CardView card; + card = createCard(game, player.getId(), "RNA", "263", 0, 0, 0); // mountain + view.put(card.getId(), card); + card = createCard(game, player.getId(), "RNA", "185", 0, 0, 0); // Judith, the Scourge Diva + view.put(card.getId(), card); + card = createCard(game, player.getId(), "RNA", "78", 125, 89, 0); // Noxious Groodion + view.put(card.getId(), card); + card = createCard(game, player.getId(), "RNA", "14", 3, 5, 2); // Knight of Sorrows + view.put(card.getId(), card); + card = createCard(game, player.getId(), "DKA", "140", 5, 2, 2); // Huntmaster of the Fells, transforms + view.put(card.getId(), card); + card = createCard(game, player.getId(), "RNA", "221", 0, 0, 0); // Bedeck // Bedazzle + view.put(card.getId(), card); + card = createCard(game, player.getId(), "XLN", "234", 0, 0, 0); // Conqueror's Galleon + view.put(card.getId(), card); + card = createEmblem(new AjaniAdversaryOfTyrantsEmblem()); // Emblem Ajani + view.put(card.getId(), card); + card = createPlane(new AkoumPlane()); // Plane - Akoum + view.put(card.getId(), card); + + cardsPanel.setCustomCardSize(new Dimension(getCardWidth(), getCardHeight())); + cardsPanel.changeGUISize(); + + cardsPanel.loadCards(view, big, game.getId()); + } + + private int getCardWidth() { + if (GUISizeHelper.editorCardDimension == null) { + return 200; + } + return (int) (GUISizeHelper.editorCardDimension.width * cardSizeMod); + } + + private int getCardHeight() { + return (int) (1.4 * getCardWidth()); + } + + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonCancel = new javax.swing.JButton(); + cardsPanel = new mage.client.cards.CardArea(); + buttonReloadCards = new javax.swing.JButton(); + labelRenderMode = new javax.swing.JLabel(); + comboRenderMode = new javax.swing.JComboBox<>(); + sliderSize = new javax.swing.JSlider(); + labelSize = new javax.swing.JLabel(); + + buttonCancel.setText("Close"); + buttonCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonCancelActionPerformed(evt); + } + }); + + buttonReloadCards.setText("Reload cards"); + buttonReloadCards.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonReloadCardsActionPerformed(evt); + } + }); + + labelRenderMode.setText("Render mode:"); + + comboRenderMode.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{"MTGO", "Image"})); + comboRenderMode.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + comboRenderModeItemStateChanged(evt); + } + }); + + sliderSize.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + sliderSizeStateChanged(evt); + } + }); + + labelSize.setText("Card size:"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 578, Short.MAX_VALUE) + .addComponent(buttonCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(cardsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(buttonReloadCards) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(labelRenderMode) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboRenderMode, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(labelSize) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(sliderSize, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonReloadCards) + .addComponent(labelRenderMode) + .addComponent(comboRenderMode, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(labelSize)) + .addComponent(sliderSize, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cardsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 421, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(buttonCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void buttonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonCancelActionPerformed + onCancel(); + }//GEN-LAST:event_buttonCancelActionPerformed + + private void buttonReloadCardsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonReloadCardsActionPerformed + reloadCards(); + }//GEN-LAST:event_buttonReloadCardsActionPerformed + + private void comboRenderModeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_comboRenderModeItemStateChanged + reloadCards(); + }//GEN-LAST:event_comboRenderModeItemStateChanged + + private void sliderSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sliderSizeStateChanged + // from DragCardGrid + // Fraction in [-1, 1] + float sliderFrac = ((float) (sliderSize.getValue() - 50)) / 50; + // Convert to frac in [0.5, 2.0] exponentially + cardSizeMod = (float) Math.pow(2, sliderFrac); + reloadCards(); + }//GEN-LAST:event_sliderSizeStateChanged + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonCancel; + private javax.swing.JButton buttonReloadCards; + private mage.client.cards.CardArea cardsPanel; + private javax.swing.JComboBox comboRenderMode; + private javax.swing.JLabel labelRenderMode; + private javax.swing.JLabel labelSize; + private javax.swing.JSlider sliderSize; + // End of variables declaration//GEN-END:variables +} + +class TestGame extends GameImpl { + + private int numPlayers; + + public TestGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); + } + + public TestGame(final TestGame game) { + super(game); + this.numPlayers = game.numPlayers; + } + + @Override + public MatchType getGameType() { + return new TestGameType(); + } + + @Override + public int getNumPlayers() { + return numPlayers; + } + + @Override + public TestGame copy() { + return new TestGame(this); + } + +} + +class TestGameType extends MatchType { + + public TestGameType() { + this.name = "Test Game Type"; + this.maxPlayers = 10; + this.minPlayers = 3; + this.numTeams = 0; + this.useAttackOption = true; + this.useRange = true; + this.sideboardingAllowed = true; + } + + protected TestGameType(final TestGameType matchType) { + super(matchType); + } + + @Override + public TestGameType copy() { + return new TestGameType(this); + } +} diff --git a/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java index 7808406b25..761024b843 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java @@ -200,8 +200,10 @@ public class WhatsNewDialog extends MageDialog { }.getType(); try { httpCookies = gson.fromJson(sourceValue, type); - for (HttpCookie cookie : httpCookies) { - store.add(URI.create(cookie.getDomain()), cookie); + if (httpCookies != null) { + for (HttpCookie cookie : httpCookies) { + store.add(URI.create(cookie.getDomain()), cookie); + } } } catch (Exception e) { logger.error("Wrong news page cookies", e); diff --git a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java index 9cbff6f1cd..d5b41eb9e0 100644 --- a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java +++ b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java @@ -136,7 +136,7 @@ public class DraftPanel extends javax.swing.JPanel { if (isLogging()) { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss"); - String logFilename = "Draft_" + sdf.format(new Date()) + '_' + draftId + ".txt"; + String logFilename = "Draft_" + sdf.format(new Date()) + '_' + draftId + ".draft"; draftLogger = new DraftPickLogger(new File("gamelogs"), logFilename); } else { draftLogger = new DraftPickLogger(); diff --git a/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java b/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java index 08a6e2a41e..c8bb72c81a 100644 --- a/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java @@ -1,254 +1,245 @@ /* - * BattlefieldPanel.java - * - * Created on 10-Jan-2010, 10:43:14 PM - */ -package mage.client.game; + * BattlefieldPanel.java + * + * Created on 10-Jan-2010, 10:43:14 PM + */ + package mage.client.game; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import javax.swing.JComponent; -import javax.swing.JLayeredPane; -import javax.swing.JScrollPane; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; -import mage.cards.MagePermanent; -import mage.client.cards.BigCard; -import mage.client.cards.Permanent; -import mage.client.plugins.impl.Plugins; -import mage.client.util.Config; -import mage.client.util.GUISizeHelper; -import mage.client.util.audio.AudioManager; -import mage.client.util.layout.CardLayoutStrategy; -import mage.client.util.layout.impl.OldCardLayoutStrategy; -import mage.view.CounterView; -import mage.view.PermanentView; + import mage.cards.MagePermanent; + import mage.client.cards.BigCard; + import mage.client.cards.Permanent; + import mage.client.dialog.PreferencesDialog; + import mage.client.plugins.impl.Plugins; + import mage.client.util.Config; + import mage.client.util.GUISizeHelper; + import mage.client.util.audio.AudioManager; + import mage.client.util.layout.CardLayoutStrategy; + import mage.client.util.layout.impl.OldCardLayoutStrategy; + import mage.view.CounterView; + import mage.view.PermanentView; -/** - * - * @author BetaSteward_at_googlemail.com - */ -public class BattlefieldPanel extends javax.swing.JLayeredPane { + import javax.swing.*; + import javax.swing.border.Border; + import javax.swing.border.EmptyBorder; + import java.awt.*; + import java.awt.event.ComponentAdapter; + import java.awt.event.ComponentEvent; + import java.util.List; + import java.util.*; + import java.util.Map.Entry; - private final Map permanents = new LinkedHashMap<>(); - private UUID gameId; - private BigCard bigCard; - private final Map uiComponentsList = new HashMap<>(); + /** + * @author BetaSteward_at_googlemail.com + */ + public class BattlefieldPanel extends javax.swing.JLayeredPane { - protected Map battlefield; - private Dimension cardDimension; + private final Map permanents = new LinkedHashMap<>(); + private UUID gameId; + private BigCard bigCard; + private final Map uiComponentsList = new HashMap<>(); - private JLayeredPane jPanel; - private JScrollPane jScrollPane; - private int width; + protected Map battlefield; + private Dimension cardDimension; - private final CardLayoutStrategy layoutStrategy = new OldCardLayoutStrategy(); + private JLayeredPane jPanel; + private JScrollPane jScrollPane; + private int width; - //private static int iCounter = 0; - private boolean addedPermanent; - private boolean addedArtifact; - private boolean addedCreature; + private final CardLayoutStrategy layoutStrategy = new OldCardLayoutStrategy(); - private boolean removedCreature; - // defines if the battlefield is within a top (means top row of player panels) or a bottom player panel - private boolean topPanelBattlefield; + //private static int iCounter = 0; + private boolean addedPermanent; + private boolean addedArtifact; + private boolean addedCreature; - /** - * Creates new form BattlefieldPanel - */ - public BattlefieldPanel() { - uiComponentsList.put("battlefieldPanel", this); - initComponents(); - uiComponentsList.put("jPanel", jPanel); - setGUISize(); + private boolean removedCreature; + // defines if the battlefield is within a top (means top row of player panels) or a bottom player panel + private boolean topPanelBattlefield; - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - int width = e.getComponent().getWidth(); - int height = e.getComponent().getHeight(); - BattlefieldPanel.this.jScrollPane.setSize(width, height); - BattlefieldPanel.this.width = width; - sortLayout(); - } - }); - } + /** + * Creates new form BattlefieldPanel + */ + public BattlefieldPanel() { + uiComponentsList.put("battlefieldPanel", this); + initComponents(); + uiComponentsList.put("jPanel", jPanel); + setGUISize(); - public void init(UUID gameId, BigCard bigCard) { - this.gameId = gameId; - this.bigCard = bigCard; - } + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + int width = e.getComponent().getWidth(); + int height = e.getComponent().getHeight(); + BattlefieldPanel.this.jScrollPane.setSize(width, height); + BattlefieldPanel.this.width = width; + sortLayout(); + } + }); + } - public void cleanUp() { - for (Component c : this.jPanel.getComponents()) { - if (c instanceof Permanent || c instanceof MagePermanent) { - this.jPanel.remove(c); - } - } - permanents.clear(); - // Plugins.getInstance().sortPermanents(uiComponentsList, permanents.values()); - this.bigCard = null; - } + public void init(UUID gameId, BigCard bigCard) { + this.gameId = gameId; + this.bigCard = bigCard; + } - public void changeGUISize() { - setGUISize(); - sortLayout(); - } + public void cleanUp() { + for (Component c : this.jPanel.getComponents()) { + if (c instanceof Permanent || c instanceof MagePermanent) { + this.jPanel.remove(c); + } + } + permanents.clear(); + // Plugins.getInstance().sortPermanents(uiComponentsList, permanents.values()); + this.bigCard = null; + } - private void setGUISize() { - jScrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(GUISizeHelper.scrollBarSize, 0)); - jScrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(0, GUISizeHelper.scrollBarSize)); - cardDimension = GUISizeHelper.battlefieldCardMaxDimension; - } + public void changeGUISize() { + setGUISize(); + sortLayout(); + } - public boolean isTopPanelBattlefield() { - return topPanelBattlefield; - } + private void setGUISize() { + jScrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(GUISizeHelper.scrollBarSize, 0)); + jScrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(0, GUISizeHelper.scrollBarSize)); + cardDimension = GUISizeHelper.battlefieldCardMaxDimension; + } - public void setTopPanelBattlefield(boolean topPanelBattlefield) { - this.topPanelBattlefield = topPanelBattlefield; - } + public boolean isTopPanelBattlefield() { + return topPanelBattlefield; + } - public void update(Map battlefield) { - boolean changed = false; + public void setTopPanelBattlefield(boolean topPanelBattlefield) { + this.topPanelBattlefield = topPanelBattlefield; + } - List permanentsToAdd = new ArrayList<>(); - for (PermanentView permanent : battlefield.values()) { - if (!permanent.isPhasedIn()) { - continue; - } - MagePermanent oldMagePermanent = permanents.get(permanent.getId()); - if (oldMagePermanent == null) { - permanentsToAdd.add(permanent); - changed = true; - } else { - if (!changed) { - changed = oldMagePermanent.getOriginalPermanent().isCreature() != permanent.isCreature(); - // Check if there was a chnage in the permanets that are the permanent attached to - if (!changed) { - int attachments = permanent.getAttachments() == null ? 0 : permanent.getAttachments().size(); - int attachmentsBefore = oldMagePermanent.getLinks().size(); - if (attachments != attachmentsBefore) { - changed = true; - } else if (attachments > 0) { - Set attachmentIds = new HashSet<>(permanent.getAttachments()); - for (MagePermanent magePermanent : oldMagePermanent.getLinks()) { - if (!attachmentIds.contains(magePermanent.getOriginalPermanent().getId())) { - // that means that the amount of attachments is the same - // but they are different: - // we've just found an attachment on previous view - // that doesn't exist anymore on current view - changed = true; - break; - } - } - } - } - // Check if permanents it now attached to another or no permanent - if (!changed) { - UUID attachedToIdBefore = oldMagePermanent.getOriginalPermanent().getAttachedTo(); - UUID attachedToId = permanent.getAttachedTo(); - if (attachedToIdBefore == null && attachedToId != null || attachedToId == null && attachedToIdBefore != null - || (attachedToIdBefore != null && !attachedToIdBefore.equals(attachedToId))) { - changed = true; - } - } - // Check for changes in the counters of the permanent - if (!changed) { - List counters1 = oldMagePermanent.getOriginalPermanent().getCounters(); - List counters2 = permanent.getCounters(); - if (counters1 == null && counters2 != null || counters1 != null && counters2 == null) { - changed = true; - } else if (counters1 != null && counters2 != null && counters1.size() != counters2.size()) { - changed = true; - } - } + public void update(Map battlefield) { + boolean changed = false; - } - oldMagePermanent.update(permanent); - } - } + List permanentsToAdd = new ArrayList<>(); + for (PermanentView permanent : battlefield.values()) { + if (!permanent.isPhasedIn()) { + continue; + } + MagePermanent oldMagePermanent = permanents.get(permanent.getId()); + if (oldMagePermanent == null) { + permanentsToAdd.add(permanent); + changed = true; + } else { + if (!changed) { + changed = oldMagePermanent.getOriginalPermanent().isCreature() != permanent.isCreature(); + // Check if there was a chnage in the permanets that are the permanent attached to + if (!changed) { + int attachments = permanent.getAttachments() == null ? 0 : permanent.getAttachments().size(); + int attachmentsBefore = oldMagePermanent.getLinks().size(); + if (attachments != attachmentsBefore) { + changed = true; + } else if (attachments > 0) { + Set attachmentIds = new HashSet<>(permanent.getAttachments()); + for (MagePermanent magePermanent : oldMagePermanent.getLinks()) { + if (!attachmentIds.contains(magePermanent.getOriginalPermanent().getId())) { + // that means that the amount of attachments is the same + // but they are different: + // we've just found an attachment on previous view + // that doesn't exist anymore on current view + changed = true; + break; + } + } + } + } + // Check if permanents it now attached to another or no permanent + if (!changed) { + UUID attachedToIdBefore = oldMagePermanent.getOriginalPermanent().getAttachedTo(); + UUID attachedToId = permanent.getAttachedTo(); + if (attachedToIdBefore == null && attachedToId != null || attachedToId == null && attachedToIdBefore != null + || (attachedToIdBefore != null && !attachedToIdBefore.equals(attachedToId))) { + changed = true; + } + } + // Check for changes in the counters of the permanent + if (!changed) { + List counters1 = oldMagePermanent.getOriginalPermanent().getCounters(); + List counters2 = permanent.getCounters(); + if (counters1 == null && counters2 != null || counters1 != null && counters2 == null) { + changed = true; + } else if (counters1 != null && counters2 != null && counters1.size() != counters2.size()) { + changed = true; + } + } - addedArtifact = addedCreature = addedPermanent = false; + } + oldMagePermanent.update(permanent); + } + } - int count = permanentsToAdd.size(); - for (PermanentView permanent : permanentsToAdd) { - addPermanent(permanent, count); - } + addedArtifact = addedCreature = addedPermanent = false; - if (addedArtifact) { - AudioManager.playAddArtifact(); - } else if (addedCreature) { - AudioManager.playSummon(); - } else if (addedPermanent) { - AudioManager.playAddPermanent(); - } + int count = permanentsToAdd.size(); + for (PermanentView permanent : permanentsToAdd) { + addPermanent(permanent, count); + } - removedCreature = false; + if (addedArtifact) { + AudioManager.playAddArtifact(); + } else if (addedCreature) { + AudioManager.playSummon(); + } else if (addedPermanent) { + AudioManager.playAddPermanent(); + } - for (Iterator> iterator = permanents.entrySet().iterator(); iterator.hasNext();) { - Entry entry = iterator.next(); - if (!battlefield.containsKey(entry.getKey()) || !battlefield.get(entry.getKey()).isPhasedIn()) { - removePermanent(entry.getKey(), 1); - iterator.remove(); - changed = true; - } - } + removedCreature = false; - if (removedCreature) { - AudioManager.playDiedCreature(); - } + for (Iterator> iterator = permanents.entrySet().iterator(); iterator.hasNext(); ) { + Entry entry = iterator.next(); + if (!battlefield.containsKey(entry.getKey()) || !battlefield.get(entry.getKey()).isPhasedIn()) { + removePermanent(entry.getKey(), 1); + iterator.remove(); + changed = true; + } + } - if (changed) { - this.battlefield = battlefield; - sortLayout(); - } - } + if (removedCreature) { + AudioManager.playDiedCreature(); + } - public void sortLayout() { - if (battlefield == null || this.getWidth() < 1) { // Can't do layout when panel is not sized yet - return; - } + if (changed) { + this.battlefield = battlefield; + sortLayout(); + } + } - layoutStrategy.doLayout(this, width); + public void sortLayout() { + if (battlefield == null || this.getWidth() < 1) { // Can't do layout when panel is not sized yet + return; + } - this.jScrollPane.repaint(); - this.jScrollPane.revalidate(); + layoutStrategy.doLayout(this, width); - invalidate(); - repaint(); - } + this.jScrollPane.repaint(); + this.jScrollPane.revalidate(); - private void addPermanent(PermanentView permanent, final int count) { - if (cardDimension == null) { - cardDimension = new Dimension(Config.dimensions.getFrameWidth(), Config.dimensions.getFrameHeight()); - } - final MagePermanent perm = Plugins.instance.getMagePermanent(permanent, bigCard, cardDimension, gameId, true); + invalidate(); + repaint(); + } - permanents.put(permanent.getId(), perm); + private void addPermanent(PermanentView permanent, final int count) { + if (cardDimension == null) { + cardDimension = new Dimension(Config.dimensions.getFrameWidth(), Config.dimensions.getFrameHeight()); + } + final MagePermanent perm = Plugins.instance.getMagePermanent(permanent, bigCard, cardDimension, gameId, true, PreferencesDialog.getRenderMode()); - BattlefieldPanel.this.jPanel.add(perm, 10); - //this.jPanel.add(perm); - if (!Plugins.instance.isCardPluginLoaded()) { - moveToFront(perm); - perm.update(permanent); - } else { - moveToFront(jPanel); - Plugins.instance.onAddCard(perm, 1); + permanents.put(permanent.getId(), perm); + + BattlefieldPanel.this.jPanel.add(perm, 10); + //this.jPanel.add(perm); + if (!Plugins.instance.isCardPluginLoaded()) { + moveToFront(perm); + perm.update(permanent); + } else { + moveToFront(jPanel); + Plugins.instance.onAddCard(perm, 1); /*Thread t = new Thread(new Runnable() { @Override public void run() { @@ -258,76 +249,76 @@ public class BattlefieldPanel extends javax.swing.JLayeredPane { synchronized (this) { threads.add(t); }*/ - } + } - if (permanent.isArtifact()) { - addedArtifact = true; - } else if (permanent.isCreature()) { - addedCreature = true; - } else { - addedPermanent = true; - } - } + if (permanent.isArtifact()) { + addedArtifact = true; + } else if (permanent.isCreature()) { + addedCreature = true; + } else { + addedPermanent = true; + } + } - private void removePermanent(UUID permanentId, final int count) { - for (Component c : this.jPanel.getComponents()) { - final Component comp = c; - if (comp instanceof Permanent) { - if (((Permanent) comp).getPermanentId().equals(permanentId)) { - comp.setVisible(false); - this.jPanel.remove(comp); - } - } else if (comp instanceof MagePermanent) { - if (((MagePermanent) comp).getOriginal().getId().equals(permanentId)) { - Thread t = new Thread(() -> { - Plugins.instance.onRemoveCard((MagePermanent) comp, count); - comp.setVisible(false); - BattlefieldPanel.this.jPanel.remove(comp); - }); - t.start(); - } - if (((MagePermanent) comp).getOriginal().isCreature()) { - removedCreature = true; - } - } - } - } + private void removePermanent(UUID permanentId, final int count) { + for (Component c : this.jPanel.getComponents()) { + final Component comp = c; + if (comp instanceof Permanent) { + if (((Permanent) comp).getPermanentId().equals(permanentId)) { + comp.setVisible(false); + this.jPanel.remove(comp); + } + } else if (comp instanceof MagePermanent) { + if (((MagePermanent) comp).getOriginal().getId().equals(permanentId)) { + Thread t = new Thread(() -> { + Plugins.instance.onRemoveCard((MagePermanent) comp, count); + comp.setVisible(false); + BattlefieldPanel.this.jPanel.remove(comp); + }); + t.start(); + } + if (((MagePermanent) comp).getOriginal().isCreature()) { + removedCreature = true; + } + } + } + } - @Override - public boolean isOptimizedDrawingEnabled() { - return false; - } + @Override + public boolean isOptimizedDrawingEnabled() { + return false; + } - public Map getPermanents() { - return permanents; - } + public Map getPermanents() { + return permanents; + } - private void initComponents() { - setOpaque(false); + private void initComponents() { + setOpaque(false); - jPanel = new JLayeredPane(); - jPanel.setLayout(null); - jPanel.setOpaque(false); - jScrollPane = new JScrollPane(jPanel); + jPanel = new JLayeredPane(); + jPanel.setLayout(null); + jPanel.setOpaque(false); + jScrollPane = new JScrollPane(jPanel); - Border empty = new EmptyBorder(0, 0, 0, 0); - jScrollPane.setBorder(empty); - jScrollPane.setViewportBorder(empty); - jScrollPane.setOpaque(false); - jScrollPane.getViewport().setOpaque(false); + Border empty = new EmptyBorder(0, 0, 0, 0); + jScrollPane.setBorder(empty); + jScrollPane.setViewportBorder(empty); + jScrollPane.setOpaque(false); + jScrollPane.getViewport().setOpaque(false); - this.add(jScrollPane); - } + this.add(jScrollPane); + } - public JLayeredPane getMainPanel() { - return jPanel; - } + public JLayeredPane getMainPanel() { + return jPanel; + } - public Map getBattlefield() { - return battlefield; - } + public Map getBattlefield() { + return battlefield; + } - public Map getUiComponentsList() { - return uiComponentsList; - } -} + public Map getUiComponentsList() { + return uiComponentsList; + } + } diff --git a/Mage.Client/src/main/java/mage/client/plugins/MagePlugins.java b/Mage.Client/src/main/java/mage/client/plugins/MagePlugins.java index 5574f1c8c3..73b5565800 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/MagePlugins.java +++ b/Mage.Client/src/main/java/mage/client/plugins/MagePlugins.java @@ -1,10 +1,5 @@ package mage.client.plugins; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.Map; -import java.util.UUID; -import javax.swing.*; import mage.cards.MageCard; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; @@ -12,6 +7,12 @@ import mage.client.cards.BigCard; import mage.view.CardView; import mage.view.PermanentView; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Map; +import java.util.UUID; + public interface MagePlugins { void loadPlugins(); @@ -22,9 +23,9 @@ public interface MagePlugins { JComponent updateTablePanel(Map ui); - MagePermanent getMagePermanent(PermanentView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage); + MagePermanent getMagePermanent(PermanentView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, int renderMode); - MageCard getMageCard(CardView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, boolean previewable); + MageCard getMageCard(CardView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, boolean previewable, int renderMode); boolean isThemePluginLoaded(); 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 afd084b88f..cf5a65eee0 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 @@ -30,8 +30,8 @@ import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; -import java.util.*; import java.util.List; +import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -154,24 +154,22 @@ public class MageActionCallback implements ActionCallback { try { final Component popupContainer = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER); - Component popup2 = MageFrame.getUI().getComponent(MageComponents.CARD_INFO_PANE); - - ((CardInfoPane) popup2).setCard(data.getCard(), popupContainer); - - showPopup(popupContainer, popup2); - + Component popupInfo = MageFrame.getUI().getComponent(MageComponents.CARD_INFO_PANE); + ((CardInfoPane) popupInfo).setCard(data.getCard(), popupContainer); + showPopup(popupContainer, popupInfo); } catch (InterruptedException e) { - LOGGER.warn(e.getMessage()); + LOGGER.error("Can't show card tooltip", e); + Thread.currentThread().interrupt(); } } public void showPopup(final Component popupContainer, final Component infoPane) throws InterruptedException { final Component c = MageFrame.getUI().getComponent(MageComponents.DESKTOP_PANE); SwingUtilities.invokeLater(() -> { - if (!popupTextWindowOpen - || enlargedWindowState != EnlargedWindowState.CLOSED) { + if (!popupTextWindowOpen || enlargedWindowState != EnlargedWindowState.CLOSED) { return; } + if (data.getLocationOnScreen() == null) { data.setLocationOnScreen(data.getComponent().getLocationOnScreen()); } @@ -365,8 +363,6 @@ public class MageActionCallback implements ActionCallback { if (!((GamePane) topPane).getGameId().equals(data.getGameId())) { return; } - } else if (data.getGameId() != null) { - return; } hideTooltipPopup(); @@ -383,6 +379,7 @@ public class MageActionCallback implements ActionCallback { ArrowUtil.drawArrowsForPairedCards(data, parentPoint); ArrowUtil.drawArrowsForBandedCards(data, parentPoint); ArrowUtil.drawArrowsForEnchantPlayers(data, parentPoint); + tooltipCard = data.getCard(); showTooltipPopup(data, parentComponent, parentPoint); } @@ -414,13 +411,9 @@ public class MageActionCallback implements ActionCallback { @Override public void hideOpenComponents() { - this.hideTooltipPopup(); - this.hideEnlargedCard(); + hideAll(null); } - /** - * Hides the text popup window - */ public void hideTooltipPopup() { this.tooltipCard = null; if (tooltipPopup != null) { @@ -433,11 +426,11 @@ public class MageActionCallback implements ActionCallback { if (SessionHandler.getSession() == null) { return; } - // set enlarged card display to visible = false Component popupContainer = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER); popupContainer.setVisible(false); - } catch (Exception e2) { - LOGGER.warn("Can't set tooltip to visible = false", e2); + } catch (InterruptedException e) { + LOGGER.error("Can't hide card tooltip", e); + Thread.currentThread().interrupt(); } } diff --git a/Mage.Client/src/main/java/mage/client/plugins/impl/Plugins.java b/Mage.Client/src/main/java/mage/client/plugins/impl/Plugins.java index e63d104219..f941944178 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/impl/Plugins.java +++ b/Mage.Client/src/main/java/mage/client/plugins/impl/Plugins.java @@ -1,12 +1,5 @@ package mage.client.plugins.impl; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import javax.swing.JComponent; import mage.cards.MageCard; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; @@ -27,12 +20,20 @@ import mage.view.PermanentView; import net.xeoh.plugins.base.PluginManager; import net.xeoh.plugins.base.impl.PluginManagerFactory; import net.xeoh.plugins.base.util.uri.ClassURI; - import org.apache.log4j.Logger; import org.mage.plugins.card.CardPluginImpl; -import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; import org.mage.plugins.theme.ThemePluginImpl; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; + public enum Plugins implements MagePlugins { instance; public static final String PLUGINS_DIRECTORY = "plugins"; @@ -92,24 +93,24 @@ public enum Plugins implements MagePlugins { } @Override - public MagePermanent getMagePermanent(PermanentView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage) { + public MagePermanent getMagePermanent(PermanentView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, int renderMode) { if (cardPlugin != null) { mageActionCallback.refreshSession(); mageActionCallback.setCardPreviewComponent(bigCard); - return cardPlugin.getMagePermanent(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage); + return cardPlugin.getMagePermanent(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage, renderMode); } else { return new Permanent(card, bigCard, Config.dimensions, gameId); } } @Override - public MageCard getMageCard(CardView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, boolean previewable) { + public MageCard getMageCard(CardView card, BigCard bigCard, Dimension dimension, UUID gameId, boolean loadImage, boolean previewable, int renderMode) { if (cardPlugin != null) { if (previewable) { mageActionCallback.refreshSession(); mageActionCallback.setCardPreviewComponent(bigCard); } - return cardPlugin.getMageCard(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage); + return cardPlugin.getMageCard(card, dimension, gameId, mageActionCallback, false, !MageFrame.isLite() && loadImage, renderMode); } else { return new Card(card, bigCard, Config.dimensions, gameId); } diff --git a/Mage.Client/src/main/java/mage/client/preference/MagePreferences.java b/Mage.Client/src/main/java/mage/client/preference/MagePreferences.java index 7b17194867..37e84edc6d 100644 --- a/Mage.Client/src/main/java/mage/client/preference/MagePreferences.java +++ b/Mage.Client/src/main/java/mage/client/preference/MagePreferences.java @@ -2,6 +2,7 @@ package mage.client.preference; import com.google.common.collect.Sets; import mage.client.MageFrame; +import mage.client.util.Config; import java.util.Set; import java.util.prefs.BackingStoreException; @@ -16,9 +17,13 @@ public final class MagePreferences { private static final String KEY_PASSWORD = "password"; private static final String KEY_EMAIL = "email"; private static final String KEY_AUTO_CONNECT = "autoConnect"; - private static final String NODE_KEY_IGNORE_LIST = "ignoreListString"; + private static String lastServerAddress = ""; + private static int lastServerPort = 0; + private static String lastServerUser = ""; + private static String lastServerPassword = ""; + private static Preferences prefs() { // TODO: Move MageFrame.prefs to this class. return MageFrame.getPreferences(); @@ -138,4 +143,26 @@ public final class MagePreferences { return prefs().node(NODE_KEY_IGNORE_LIST).node(serverAddress); } + public static void saveLastServer() { + lastServerAddress = getServerAddressWithDefault(Config.serverName); + lastServerPort = getServerPortWithDefault(Config.port); + lastServerUser = getUserName(lastServerAddress); + lastServerPassword = getPassword(lastServerAddress); + } + + public static String getLastServerAddress() { + return lastServerAddress.isEmpty() ? getServerAddress() : lastServerAddress; + } + + public static int getLastServerPort() { + return lastServerPort == 0 ? getServerPort() : lastServerPort; + } + + public static String getLastServerUser() { + return lastServerUser.isEmpty() ? getUserName(getLastServerAddress()) : lastServerUser; + } + + public static String getLastServerPassword() { + return lastServerPassword.isEmpty() ? getPassword(getLastServerAddress()) : lastServerPassword; + } } diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPane.java b/Mage.Client/src/main/java/mage/client/table/TablesPane.java index 3b6c6533b5..5e781aac79 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPane.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPane.java @@ -105,7 +105,7 @@ public class TablesPane extends MagePane { @Override public void activated() { - tablesPanel.startTasks(); + tablesPanel.startUpdateTasks(false); } @Override diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.form b/Mage.Client/src/main/java/mage/client/table/TablesPanel.form index 52b892737d..70f04d7ec3 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.form +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.form @@ -43,8 +43,11 @@ - - + + + + + @@ -60,10 +63,16 @@ - + - + + + + + + + @@ -506,15 +515,26 @@ - + - + - + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index da3d8402ca..9544426506 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -1,5 +1,6 @@ package mage.client.table; +import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; import mage.client.MageFrame; import mage.client.SessionHandler; @@ -16,6 +17,8 @@ import mage.constants.*; import mage.game.match.MatchOptions; import mage.players.PlayerType; import mage.remote.MageRemoteException; +import mage.util.DeckUtil; +import mage.util.RandomUtil; import mage.view.MatchView; import mage.view.RoomUsersView; import mage.view.TableView; @@ -65,6 +68,7 @@ public class TablesPanel extends javax.swing.JPanel { public static final int REFRESH_ACTIVE_TABLES_SECS = 5; public static final int REFRESH_FINISHED_TABLES_SECS = 30; public static final int REFRESH_PLAYERS_SECS = 10; + public static final double REFRESH_TIMEOUTS_INCREASE_FACTOR = 0.8; // can increase timeouts by 80% (0.8) private final TablesTableModel tableModel; private final MatchesTableModel matchesModel; @@ -211,6 +215,12 @@ public class TablesPanel extends javax.swing.JPanel { return res; } + public static int randomizeTimout(int minTimout) { + // randomize timeouts to fix calls waves -- slow server can creates queue and moves all clients to same call window + int increase = (int) (minTimout * REFRESH_TIMEOUTS_INCREASE_FACTOR); + return minTimout + RandomUtil.nextInt(increase); + } + /** * Creates new form TablesPanel @@ -513,6 +523,7 @@ public class TablesPanel extends javax.swing.JPanel { public void cleanUp() { saveGuiSettings(); chatPanelMain.cleanUp(); + stopTasks(); } public void changeGUISize() { @@ -632,23 +643,31 @@ public class TablesPanel extends javax.swing.JPanel { } } - public void startTasks() { + public void startUpdateTasks(boolean refreshImmediately) { if (SessionHandler.getSession() != null) { - if (updateTablesTask == null || updateTablesTask.isDone()) { + // active tables and server messages + if (updateTablesTask == null || updateTablesTask.isDone() || refreshImmediately) { + if (updateTablesTask != null) updateTablesTask.cancel(true); updateTablesTask = new UpdateTablesTask(roomId, this); updateTablesTask.execute(); } - if (updatePlayersTask == null || updatePlayersTask.isDone()) { - updatePlayersTask = new UpdatePlayersTask(roomId, this.chatPanelMain); - updatePlayersTask.execute(); - } + + // finished tables if (this.btnStateFinished.isSelected()) { - if (updateMatchesTask == null || updateMatchesTask.isDone()) { + if (updateMatchesTask == null || updateMatchesTask.isDone() || refreshImmediately) { + if (updateMatchesTask != null) updateMatchesTask.cancel(true); updateMatchesTask = new UpdateMatchesTask(roomId, this); updateMatchesTask.execute(); } - } else if (updateMatchesTask != null) { - updateMatchesTask.cancel(true); + } else { + if (updateMatchesTask != null) updateMatchesTask.cancel(true); + } + + // players list + if (updatePlayersTask == null || updatePlayersTask.isDone() || refreshImmediately) { + if (updatePlayersTask != null) updatePlayersTask.cancel(true); + updatePlayersTask = new UpdatePlayersTask(roomId, this.chatPanelMain); + updatePlayersTask.execute(); } } } @@ -669,7 +688,8 @@ public class TablesPanel extends javax.swing.JPanel { this.roomId = roomId; UUID chatRoomId = null; if (SessionHandler.getSession() != null) { - btnQuickStart.setVisible(SessionHandler.isTestMode()); + btnQuickStartDuel.setVisible(SessionHandler.isTestMode()); + btnQuickStartCommander.setVisible(SessionHandler.isTestMode()); gameChooser.init(); chatRoomId = SessionHandler.getRoomChatId(roomId).orElse(null); } @@ -687,7 +707,7 @@ public class TablesPanel extends javax.swing.JPanel { } if (chatRoomId != null) { this.chatPanelMain.getUserChatPanel().connect(chatRoomId); - startTasks(); + startUpdateTasks(true); this.setVisible(true); this.repaint(); } else { @@ -695,7 +715,7 @@ public class TablesPanel extends javax.swing.JPanel { } //tableModel.setSession(session); - reloadMessages(); + reloadServerMessages(); MageFrame.getUI().addButton(MageComponents.NEW_GAME_BUTTON, btnNewTable); @@ -704,7 +724,7 @@ public class TablesPanel extends javax.swing.JPanel { } - protected void reloadMessages() { + protected void reloadServerMessages() { // reload server messages java.util.List serverMessages = SessionHandler.getServerMessages(); synchronized (this) { @@ -954,7 +974,8 @@ public class TablesPanel extends javax.swing.JPanel { jSeparator5 = new javax.swing.JToolBar.Separator(); btnOpen = new javax.swing.JToggleButton(); btnPassword = new javax.swing.JToggleButton(); - btnQuickStart = new javax.swing.JButton(); + btnQuickStartDuel = new javax.swing.JButton(); + btnQuickStartCommander = new javax.swing.JButton(); jSplitPane1 = new javax.swing.JSplitPane(); jPanelTables = new javax.swing.JPanel(); jSplitPaneTables = new javax.swing.JSplitPane(); @@ -1374,13 +1395,23 @@ public class TablesPanel extends javax.swing.JPanel { }); filterBar2.add(btnPassword); - btnQuickStart.setText("Quick Start"); - btnQuickStart.setFocusable(false); - btnQuickStart.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - btnQuickStart.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - btnQuickStart.addActionListener(new java.awt.event.ActionListener() { + btnQuickStartDuel.setText("Quick start duel"); + btnQuickStartDuel.setFocusable(false); + btnQuickStartDuel.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnQuickStartDuel.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnQuickStartDuel.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - btnQuickStartActionPerformed(evt); + btnQuickStartDuelActionPerformed(evt); + } + }); + + btnQuickStartCommander.setText("Quick start commander"); + btnQuickStartCommander.setFocusable(false); + btnQuickStartCommander.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnQuickStartCommander.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnQuickStartCommander.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnQuickStartCommanderActionPerformed(evt); } }); @@ -1398,8 +1429,10 @@ public class TablesPanel extends javax.swing.JPanel { .addComponent(filterBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(filterBar2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnQuickStart) - .addContainerGap(792, Short.MAX_VALUE)) + .addGroup(jPanelTopLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(btnQuickStartDuel) + .addComponent(btnQuickStartCommander)) + .addContainerGap(734, Short.MAX_VALUE)) ); jPanelTopLayout.setVerticalGroup( jPanelTopLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1412,9 +1445,13 @@ public class TablesPanel extends javax.swing.JPanel { .addGroup(jPanelTopLayout.createSequentialGroup() .addGroup(jPanelTopLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(filterBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(btnQuickStart)) + .addComponent(btnQuickStartDuel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(filterBar2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(jPanelTopLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filterBar2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanelTopLayout.createSequentialGroup() + .addComponent(btnQuickStartCommander) + .addGap(0, 0, Short.MAX_VALUE))))) .addContainerGap()) ); @@ -1518,16 +1555,23 @@ public class TablesPanel extends javax.swing.JPanel { newTournamentDialog.showDialog(roomId); }//GEN-LAST:event_btnNewTournamentActionPerformed - private void btnQuickStartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnQuickStartActionPerformed + private void createTestGame(String gameName, String gameType) { TableView table; try { - File f = new File("test.dck"); + String testDeckFile = "test.dck"; + File f = new File(testDeckFile); if (!f.exists()) { - JOptionPane.showMessageDialog(null, "Couldn't find test.dck file for quick game start", "Error", JOptionPane.ERROR_MESSAGE); - return; + // default test deck + testDeckFile = DeckUtil.writeTextToTempFile("" + + "5 Swamp" + System.lineSeparator() + + "5 Forest" + System.lineSeparator() + + "5 Island" + System.lineSeparator() + + "5 Mountain" + System.lineSeparator() + + "5 Plains"); } + DeckCardLists testDeck = DeckImporter.importDeckFromFile(testDeckFile); - MatchOptions options = new MatchOptions("1", "Two Player Duel", false, 2); + MatchOptions options = new MatchOptions(gameName, gameType, false, 2); options.getPlayerTypes().add(PlayerType.HUMAN); options.getPlayerTypes().add(PlayerType.COMPUTER_MAD); options.setDeckType("Limited"); @@ -1544,13 +1588,17 @@ public class TablesPanel extends javax.swing.JPanel { options.setBannedUsers(IgnoreList.ignoreList(serverAddress)); table = SessionHandler.createTable(roomId, options); - SessionHandler.joinTable(roomId, table.getTableId(), "Human", PlayerType.HUMAN, 1, DeckImporter.importDeckFromFile("test.dck"), ""); - SessionHandler.joinTable(roomId, table.getTableId(), "Computer", PlayerType.COMPUTER_MAD, 5, DeckImporter.importDeckFromFile("test.dck"), ""); + SessionHandler.joinTable(roomId, table.getTableId(), "Human", PlayerType.HUMAN, 1, testDeck, ""); + SessionHandler.joinTable(roomId, table.getTableId(), "Computer", PlayerType.COMPUTER_MAD, 5, testDeck, ""); SessionHandler.startMatch(roomId, table.getTableId()); } catch (HeadlessException ex) { handleError(ex); } - }//GEN-LAST:event_btnQuickStartActionPerformed + } + + private void btnQuickStartDuelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnQuickStartDuelActionPerformed + createTestGame("Test duel", "Two Player Duel"); + }//GEN-LAST:event_btnQuickStartDuelActionPerformed private void btnNewTableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnNewTableActionPerformed newTableDialog.showDialog(roomId); @@ -1580,7 +1628,7 @@ public class TablesPanel extends javax.swing.JPanel { } else { this.jSplitPaneTables.setDividerLocation(this.jPanelTables.getHeight()); } - this.startTasks(); + this.startUpdateTasks(true); }//GEN-LAST:event_btnStateFinishedActionPerformed private void btnRatedbtnFilterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRatedbtnFilterActionPerformed @@ -1603,6 +1651,10 @@ public class TablesPanel extends javax.swing.JPanel { MageFrame.getInstance().showWhatsNewDialog(true); }//GEN-LAST:event_buttonWhatsNewActionPerformed + private void btnQuickStartCommanderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnQuickStartCommanderActionPerformed + createTestGame("Test commander", "Commander Two Player Duel"); + }//GEN-LAST:event_btnQuickStartCommanderActionPerformed + private void handleError(Exception ex) { LOGGER.fatal("Error loading deck: ", ex); JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Error loading deck.", "Error", JOptionPane.ERROR_MESSAGE); @@ -1623,7 +1675,8 @@ public class TablesPanel extends javax.swing.JPanel { private javax.swing.JButton btnNewTournament; private javax.swing.JToggleButton btnOpen; private javax.swing.JToggleButton btnPassword; - private javax.swing.JButton btnQuickStart; + private javax.swing.JButton btnQuickStartCommander; + private javax.swing.JButton btnQuickStartDuel; private javax.swing.JToggleButton btnRated; private javax.swing.JToggleButton btnSkillBeginner; private javax.swing.JToggleButton btnSkillCasual; @@ -1665,6 +1718,7 @@ class UpdateTablesTask extends SwingWorker> { private final UUID roomId; private final TablesPanel panel; + private boolean isFirstRun = true; private static final Logger logger = Logger.getLogger(UpdateTablesTask.class); @@ -1683,8 +1737,7 @@ class UpdateTablesTask extends SwingWorker> { if (tables != null) { this.publish(tables); } - - TimeUnit.SECONDS.sleep(TablesPanel.REFRESH_ACTIVE_TABLES_SECS); + TimeUnit.SECONDS.sleep(TablesPanel.randomizeTimout(TablesPanel.REFRESH_ACTIVE_TABLES_SECS)); } return null; } @@ -1692,10 +1745,13 @@ class UpdateTablesTask extends SwingWorker> { @Override protected void process(java.util.List> view) { panel.updateTables(view.get(0)); + + // update server messages count++; - if (count > 60) { + if (isFirstRun || count > 60) { count = 0; - panel.reloadMessages(); + isFirstRun = false; + panel.reloadServerMessages(); } } @@ -1728,7 +1784,7 @@ class UpdatePlayersTask extends SwingWorker> { protected Void doInBackground() throws Exception { while (!isCancelled()) { this.publish(SessionHandler.getRoomUsers(roomId)); - TimeUnit.SECONDS.sleep(TablesPanel.REFRESH_PLAYERS_SECS); + TimeUnit.SECONDS.sleep(TablesPanel.randomizeTimout(TablesPanel.REFRESH_PLAYERS_SECS)); } return null; } @@ -1765,11 +1821,8 @@ class UpdateMatchesTask extends SwingWorker> { @Override protected Void doInBackground() throws Exception { while (!isCancelled()) { - Collection matches = SessionHandler.getFinishedMatches(roomId); - if (!matches.isEmpty()) { - this.publish(matches); - } - TimeUnit.SECONDS.sleep(TablesPanel.REFRESH_FINISHED_TABLES_SECS); + this.publish(SessionHandler.getFinishedMatches(roomId)); + TimeUnit.SECONDS.sleep(TablesPanel.randomizeTimout(TablesPanel.REFRESH_FINISHED_TABLES_SECS)); } return null; } diff --git a/Mage.Client/src/main/java/mage/client/util/gui/ArrowUtil.java b/Mage.Client/src/main/java/mage/client/util/gui/ArrowUtil.java index c6088f2089..243b47dc7c 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/ArrowUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/ArrowUtil.java @@ -16,14 +16,15 @@ import java.util.UUID; */ public final class ArrowUtil { - private ArrowUtil() {} + private ArrowUtil() { + } public static void drawArrowsForPairedCards(TransferData data, Point parentPoint) { if (data.getCard().getPairedCard() != null) { Point me = new Point(data.getLocationOnScreen()); me.translate(-parentPoint.x, -parentPoint.y); UUID uuid = data.getCard().getPairedCard(); - for (PlayAreaPanel pa : MageFrame.getGame(data.getGameId()).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(data.getGameId()).values()) { MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(uuid); if (permanent != null) { Point target = permanent.getLocationOnScreen(); @@ -38,7 +39,7 @@ public final class ArrowUtil { if (data.getCard().getBandedCards() != null && !data.getCard().getBandedCards().isEmpty()) { Point me = new Point(data.getLocationOnScreen()); me.translate(-parentPoint.x, -parentPoint.y); - for (PlayAreaPanel pa : MageFrame.getGame(data.getGameId()).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(data.getGameId()).values()) { for (UUID uuid : data.getCard().getBandedCards()) { MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(uuid); if (permanent != null) { @@ -53,7 +54,7 @@ public final class ArrowUtil { public static void drawArrowsForEnchantPlayers(TransferData data, Point parentPoint) { if (data.getGameId() != null && MageFrame.getGame(data.getGameId()) != null) { - for (PlayAreaPanel pa : MageFrame.getGame(data.getGameId()).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(data.getGameId()).values()) { PlayerPanelExt playAreaPanel = pa.getPlayerPanel(); if (playAreaPanel != null && playAreaPanel.getPlayer() != null && playAreaPanel.getPlayer().hasAttachments()) { Point me = new Point(data.getLocationOnScreen()); @@ -62,7 +63,7 @@ public final class ArrowUtil { if (attachmentId.equals(data.getCard().getId())) { Point player = pa.getLocationOnScreen(); player.translate(-parentPoint.x, -parentPoint.y); - ArrowBuilder.getBuilder().addArrow(data.getGameId(),(int) me.getX() + 35, (int) me.getY(), (int) player.getX() + 40, (int) player.getY() - 40, Color.magenta, ArrowBuilder.Type.ENCHANT_PLAYERS); + ArrowBuilder.getBuilder().addArrow(data.getGameId(), (int) me.getX() + 35, (int) me.getY(), (int) player.getX() + 40, (int) player.getY() - 40, Color.magenta, ArrowBuilder.Type.ENCHANT_PLAYERS); } } } @@ -75,7 +76,7 @@ public final class ArrowUtil { Point me = new Point(data.getLocationOnScreen()); me.translate(-parentPoint.x, -parentPoint.y); UUID uuid = data.getCard().getParentId(); - for (PlayAreaPanel pa : MageFrame.getGame(data.getGameId()).getPlayers().values()) { + for (PlayAreaPanel pa : MageFrame.getGamePlayers(data.getGameId()).values()) { MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(uuid); if (permanent != null) { Point source = permanent.getLocationOnScreen(); @@ -96,7 +97,7 @@ public final class ArrowUtil { me.translate(-parentPoint.x, -parentPoint.y); for (UUID uuid : targets) { - PlayAreaPanel p = MageFrame.getGame(data.getGameId()).getPlayers().get(uuid); + PlayAreaPanel p = MageFrame.getGamePlayers(data.getGameId()).get(uuid); if (p != null) { Point target = p.getLocationOnScreen(); target.translate(-parentPoint.x, -parentPoint.y); @@ -104,7 +105,7 @@ public final class ArrowUtil { continue; } - for (PlayAreaPanel panel : MageFrame.getGame(data.getGameId()).getPlayers().values()) { + for (PlayAreaPanel panel : MageFrame.getGamePlayers(data.getGameId()).values()) { MagePermanent permanent = panel.getBattlefieldPanel().getPermanents().get(uuid); if (permanent != null) { Point target = permanent.getLocationOnScreen(); @@ -117,7 +118,7 @@ public final class ArrowUtil { if (view != null) { CardsView graveyard = view.getGraveyard(); if (graveyard.containsKey(uuid)) { - p = MageFrame.getGame(data.getGameId()).getPlayers().get(view.getPlayerId()); + p = MageFrame.getGamePlayers(data.getGameId()).get(view.getPlayerId()); if (p != null) { Point target = p.getLocationOnScreen(); target.translate(-parentPoint.x, -parentPoint.y); 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 d075cefeeb..76a8886767 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 @@ -181,10 +181,14 @@ public final class GuiDisplayUtil { public static TextLines getTextLinesfromCardView(CardView card) { TextLines textLines = new TextLines(); + + // rules textLines.setLines(new ArrayList<>(card.getRules())); for (String rule : card.getRules()) { textLines.setBasicTextLength(textLines.getBasicTextLength() + rule.length()); } + + // counters if (card.getMageObjectType().canHaveCounters()) { ArrayList counters = new ArrayList<>(); if (card instanceof PermanentView) { @@ -212,6 +216,8 @@ public final class GuiDisplayUtil { textLines.setBasicTextLength(textLines.getBasicTextLength() + 50); } } + + // damage if (card.getMageObjectType().isPermanent() && card instanceof PermanentView) { int damage = ((PermanentView) card).getDamage(); if (damage > 0) { diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index a237318a17..5c97f2c59e 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -1,5 +1,6 @@ package org.mage.card.arcane; +import mage.MageInt; import mage.cards.action.ActionCallback; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; @@ -9,6 +10,7 @@ import mage.client.util.SoftValuesLoadingCache; import mage.components.ImagePanel; import mage.components.ImagePanelStyle; import mage.constants.AbilityType; +import mage.constants.SubType; import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; @@ -49,11 +51,16 @@ public class CardPanelComponentImpl extends CardPanel { private static final int CARD_MIN_SIZE_FOR_ICONS = 60; private static final int CARD_MAX_SIZE_FOR_ICONS = 200; + // text min size for image render mode + private static final int CARD_TITLE_FONT_MIN_SIZE = 13; + private static final int CARD_PT_FONT_MIN_SIZE = 17; + public final ScaledImagePanel imagePanel; private ImagePanel overlayPanel; private JPanel iconPanel; private JButton typeButton; + private JPanel ptPanel; private JPanel counterPanel; private JLabel loyaltyCounterLabel; @@ -67,7 +74,9 @@ public class CardPanelComponentImpl extends CardPanel { private int lastCardWidth; private final GlowText titleText; - private final GlowText ptText; + private final GlowText ptText1; + private final GlowText ptText2; + private final GlowText ptText3; private final JLabel fullImageText; private String fullImagePath = null; @@ -280,7 +289,7 @@ public class CardPanelComponentImpl extends CardPanel { // Title Text titleText = new GlowText(); - setText(getGameCard()); + setTitle(getGameCard()); // int fontSize = (int) cardHeight / 11; // titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); titleText.setForeground(Color.white); @@ -295,16 +304,19 @@ public class CardPanelComponentImpl extends CardPanel { add(fullImageText); // PT Text - ptText = new GlowText(); - if (getGameCard().isCreature()) { - ptText.setText(getGameCard().getPower() + '/' + getGameCard().getToughness()); - } else if (getGameCard().isPlanesWalker()) { - ptText.setText(getGameCard().getLoyalty()); - } -// ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - ptText.setForeground(Color.white); - ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); - add(ptText); + ptPanel = new JPanel(); + ptPanel.setOpaque(false); + ptPanel.setLayout(new BoxLayout(ptPanel, BoxLayout.X_AXIS)); + ptPanel.add(new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(Integer.MAX_VALUE, 0))); + ptText1 = new GlowText(); + ptText2 = new GlowText(); + ptText3 = new GlowText(); + updatePTTexts(getGameCard()); + ptPanel.add(ptText1); + ptPanel.add(ptText2); + ptPanel.add(ptText3); + // + add(ptPanel); // Sickness overlay BufferedImage sickness = ImageManagerImpl.instance.getSicknessImage(); @@ -349,7 +361,7 @@ public class CardPanelComponentImpl extends CardPanel { this.setCounterPanel(null); } - private void setText(CardView card) { + private void setTitle(CardView card) { titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getName()); } @@ -585,12 +597,14 @@ public class CardPanelComponentImpl extends CardPanel { boolean showText = !isAnimationPanel() && canShowCardIcons(cardWidth, hasImage); titleText.setVisible(showText); - ptText.setVisible(showText); + ptText1.setVisible(showText && !ptText1.getText().isEmpty()); + ptText2.setVisible(showText && !ptText2.getText().isEmpty()); + ptText3.setVisible(showText && !ptText3.getText().isEmpty()); fullImageText.setVisible(fullImagePath != null); if (showText) { int fontSize = cardHeight / 13; // startup font size (it same size on all zoom levels) - titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + titleText.setFont(getFont().deriveFont(Font.BOLD, Math.max(CARD_TITLE_FONT_MIN_SIZE, fontSize))); // margins from card black border to text, not need? text show up good without margins int titleMarginLeft = 0; //Math.round(28f / 672f * cardWidth); @@ -607,24 +621,60 @@ public class CardPanelComponentImpl extends CardPanel { fullImageText.setFont(getFont().deriveFont(Font.PLAIN, 10)); fullImageText.setBounds(titleText.getX(), titleText.getY(), titleText.getBounds().width, titleText.getBounds().height); - // life points location (font as title) - ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - Dimension ptSize = ptText.getPreferredSize(); - ptText.setSize(ptSize.width, ptSize.height); + // PT (font as title) + if (getGameCard().getOriginalCard() != null) { + prepareGlowFont(ptText1, Math.max(CARD_PT_FONT_MIN_SIZE, fontSize), getGameCard().getOriginalCard().getPower(), false); + prepareGlowFont(ptText2, Math.max(CARD_PT_FONT_MIN_SIZE, fontSize), null, false); + prepareGlowFont(ptText3, Math.max(CARD_PT_FONT_MIN_SIZE, fontSize), getGameCard().getOriginalCard().getToughness(), CardRendererUtils.isCardWithDamage(getGameCard())); - // right bottom corner with margin (sizes from any sample card) - int ptMarginRight = Math.round(64f / 672f * cardWidth); - int ptMarginBottom = Math.round(62f / 936f * cardHeight); + // right bottom corner with margin (sizes from any sample card) + int ptMarginRight = Math.round(64f / 672f * cardWidth); + int ptMarginBottom = Math.round(62f / 936f * cardHeight); - int ptX = cardXOffset + cardWidth - ptMarginRight - ptSize.width; - int ptY = cardYOffset + cardHeight - ptMarginBottom - ptSize.height; - ptText.setLocation(ptX, ptY); + int ptWidth = cardWidth - ptMarginRight * 2; + int ptHeight = ptText2.getHeight(); + int ptX = cardXOffset + ptMarginRight; + int ptY = cardYOffset + cardHeight - ptMarginBottom - ptHeight; + ptPanel.setBounds(ptX, ptY, ptWidth, ptHeight); + } // old version was with TEXT_GLOW_SIZE //ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); } } + private void prepareGlowFont(GlowText label, int fontSize, MageInt value, boolean drawAsDamaged) { + label.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + label.setForeground(CardRendererUtils.getCardTextColor(value, drawAsDamaged, titleText.getForeground(), true)); + Dimension ptSize = label.getPreferredSize(); + label.setSize(ptSize.width, ptSize.height); + } + + private void updatePTTexts(CardView card) { + if (card.isCreature() || card.getSubTypes().contains(SubType.VEHICLE)) { + ptText1.setText(getGameCard().getPower()); + ptText2.setText("/"); + ptText3.setText(CardRendererUtils.getCardLifeWithDamage(getGameCard())); + } else if (card.isPlanesWalker()) { + ptText1.setText(""); + ptText2.setText(""); + ptText3.setText(getGameCard().getLoyalty()); + } else { + ptText1.setText(""); + ptText2.setText(""); + ptText3.setText(""); + } + + ptText1.setForeground(Color.white); + ptText1.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + + ptText2.setForeground(Color.white); + ptText2.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + + ptText3.setForeground(Color.white); + ptText3.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + } + @Override public String toString() { return getGameCard().toString(); @@ -647,10 +697,14 @@ public class CardPanelComponentImpl extends CardPanel { // Update components if (alpha == 0) { - this.ptText.setVisible(false); + this.ptText1.setVisible(false); + this.ptText2.setVisible(false); + this.ptText3.setVisible(false); this.titleText.setVisible(false); } else if (alpha == 1.0f) { - this.ptText.setVisible(true); + this.ptText1.setVisible(true); + this.ptText2.setVisible(true); + this.ptText3.setVisible(true); this.titleText.setVisible(true); } } @@ -683,7 +737,7 @@ public class CardPanelComponentImpl extends CardPanel { UI.invokeLater(() -> { if (stamp == updateArtImageStamp) { hasImage = srcImage != null; - setText(getGameCard()); + setTitle(getGameCard()); setImage(srcImage); } }); @@ -712,7 +766,7 @@ public class CardPanelComponentImpl extends CardPanel { @Override public void showCardTitle() { displayTitleAnyway = true; - setText(getGameCard()); + setTitle(getGameCard()); } @Override @@ -720,17 +774,8 @@ public class CardPanelComponentImpl extends CardPanel { // Super super.update(card); - // Update card text - if (card.isCreature() && card.isPlanesWalker()) { - ptText.setText(card.getPower() + '/' + card.getToughness() + " (" + card.getLoyalty() + ')'); - } else if (card.isCreature()) { - ptText.setText(card.getPower() + '/' + card.getToughness()); - } else if (card.isPlanesWalker()) { - ptText.setText(card.getLoyalty()); - } else { - ptText.setText(""); - } - setText(card); + updatePTTexts(card); + setTitle(card); // Summoning Sickness overlay if (hasSickness() && card.isCreature() && isPermanent()) { diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java index cf62e7cb43..6b7dbc2274 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java @@ -297,6 +297,8 @@ public class CardPanelRenderImpl extends CardPanel { // Render with Antialialsing g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Attributes CardPanelAttributes attribs diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java index f32c08e972..42caffce72 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java @@ -1,5 +1,9 @@ package org.mage.card.arcane; +import mage.MageInt; +import mage.view.CardView; +import mage.view.PermanentView; + import java.awt.*; import java.awt.image.BufferedImage; import java.util.HashMap; @@ -10,12 +14,18 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * @author stravant@gmail.com + * @author stravant@gmail.com, JayDi85 *

* Various static utilities for use in the card renderer */ public final class CardRendererUtils { + // text colors for PT (mtgo and image render modes) + private static final Color CARD_TEXT_COLOR_GOOD_LIGHT = new Color(182, 235, 168); + private static final Color CARD_TEXT_COLOR_GOOD_DARK = new Color(52, 135, 88); + private static final Color CARD_TEXT_COLOR_BAD_LIGHT = new Color(234, 153, 153); + private static final Color CARD_TEXT_COLOR_BAD_DARK = new Color(200, 33, 33); + /** * Convert an abstract image, whose underlying implementation may or may not * be a BufferedImage into a BufferedImage by creating one and coping the @@ -53,9 +63,9 @@ public final class CardRendererUtils { int b = c.getBlue(); int alpha = c.getAlpha(); - int plus_r = (int) ((255 - r) / 2); - int plus_g = (int) ((255 - g) / 2); - int plus_b = (int) ((255 - b) / 2); + int plus_r = (255 - r) / 2; + int plus_g = (255 - g) / 2; + int plus_b = (255 - b) / 2; return new Color(r + plus_r, g + plus_g, @@ -69,9 +79,9 @@ public final class CardRendererUtils { int b = c.getBlue(); int alpha = c.getAlpha(); - int plus_r = (int) (Math.min(255 - r, r) / 2); - int plus_g = (int) (Math.min(255 - g, g) / 2); - int plus_b = (int) (Math.min(255 - b, b) / 2); + int plus_r = Math.min(255 - r, r) / 2; + int plus_g = Math.min(255 - g, g) / 2; + int plus_b = Math.min(255 - b, b) / 2; return new Color(r - plus_r, g - plus_g, @@ -195,4 +205,52 @@ public final class CardRendererUtils { return null; } } + + public static String getCardLifeWithDamage(CardView cardView) { + // life with damage + String originLife = cardView.getToughness(); + if (cardView instanceof PermanentView) { + int damage = ((PermanentView) cardView).getDamage(); + int life; + try { + life = Integer.parseInt(originLife); + originLife = String.valueOf(Math.max(0, life - damage)); + } catch (NumberFormatException e) { + // + } + } + return originLife; + } + + public static boolean isCardWithDamage(CardView cardView) { + boolean haveDamage = false; + if (cardView instanceof PermanentView) { + haveDamage = ((PermanentView) cardView).getDamage() > 0; + } + return haveDamage; + } + + public static Color getCardTextColor(MageInt value, boolean drawAsDamaged, Color defaultColor, boolean textLight) { + if (drawAsDamaged) { + return textLight ? CARD_TEXT_COLOR_BAD_LIGHT : CARD_TEXT_COLOR_BAD_DARK; + } + + // boost colorizing + if (value != null) { + int current = value.getValue(); + int origin = value.getBaseValue(); + if (origin != 0) { + if (current < origin) { + return textLight ? CARD_TEXT_COLOR_BAD_LIGHT : CARD_TEXT_COLOR_BAD_DARK; + } else if (current > origin) { + return textLight ? CARD_TEXT_COLOR_GOOD_LIGHT : CARD_TEXT_COLOR_GOOD_DARK; + } else { + return defaultColor; + } + } + } + + return defaultColor; + } + } 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 8d95a11ac0..5600c59b81 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 @@ -1,5 +1,10 @@ package org.mage.card.arcane; +import mage.client.util.ImageCaches; +import mage.client.util.SoftValuesLoadingCache; +import org.jdesktop.swingx.graphics.GraphicsUtilities; + +import javax.swing.*; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; @@ -14,17 +19,6 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; -import javax.swing.*; - -import org.jdesktop.swingx.graphics.GraphicsUtilities; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - -import mage.client.util.ImageCaches; -import mage.client.util.SoftValuesLoadingCache; - public class GlowText extends JLabel { private static final long serialVersionUID = 1827677946939348001L; @@ -123,10 +117,7 @@ public class GlowText extends JLabel { if (!Objects.equals(this.color, other.color)) { return false; } - if (!Objects.equals(this.glowColor, other.glowColor)) { - return false; - } - return true; + return Objects.equals(this.glowColor, other.glowColor); } } @@ -158,7 +149,11 @@ public class GlowText extends JLabel { return; } - g.drawImage(IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null); + g.drawImage(getGlowImage(), 0, 0, null); + } + + public BufferedImage getGlowImage() { + return IMAGE_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 d70f8fea3e..566931ef97 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 @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package org.mage.card.arcane; import mage.ObjectColor; @@ -56,13 +51,14 @@ import static org.mage.card.arcane.ManaSymbols.getSizedManaSymbol; */ /** - * @author stravant@gmail.com + * @author stravant@gmail.com, JayDi85 *

* Base rendering class for new border cards */ public class ModernCardRenderer extends CardRenderer { private static final Logger LOGGER = Logger.getLogger(ModernCardRenderer.class); + private static final GlowText glowTextRenderer = new GlowText(); /////////////////////////////////////////////////////////////////////////// // Textures for modern frame cards @@ -98,7 +94,7 @@ public class ModernCardRenderer extends CardRenderer { return new Font("Arial", Font.PLAIN, 1); } - public static final Font BASE_BELEREN_FONT = loadFont("beleren-bold"); + // public static final Font BASE_BELEREN_FONT = loadFont("beleren-bold"); public static final Paint BG_TEXTURE_WHITE = loadBackgroundTexture("white"); public static final Paint BG_TEXTURE_BLUE = loadBackgroundTexture("blue"); @@ -252,16 +248,13 @@ public class ModernCardRenderer extends CardRenderer { // Box text height boxTextHeight = getTextHeightForBoxHeight(boxHeight); boxTextOffset = (boxHeight - boxTextHeight) / 2; - // Not using Beleren for now because it looks bad at small font sizes. Maybe we want to in the future? - //boxTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, boxTextHeight); boxTextFont = new Font("Arial", Font.PLAIN, boxTextHeight); boxTextFontNarrow = new Font("Arial Narrow", Font.PLAIN, boxTextHeight); // Box text height ptTextHeight = getPTTextHeightForLineHeight(boxHeight); ptTextOffset = (boxHeight - ptTextHeight) / 2; - // Beleren font does work well for numbers though - ptTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, ptTextHeight); + ptTextFont = new Font("Arial", Font.BOLD, ptTextHeight); } @Override @@ -482,7 +475,7 @@ public class ModernCardRenderer extends CardRenderer { g.setPaint(borderPaint); if (cardView.getFrameStyle() == FrameStyle.KLD_INVENTION) { - g.drawImage(FRAME_INVENTION, 0, 0, cardWidth, cardHeight, null); + g.drawImage(FRAME_INVENTION, 3, 3, cardWidth - 6, cardHeight - 6, null); g.drawRect( totalContentInset, typeLineY, contentWidth - 1, cardHeight - borderWidth * 3 - typeLineY - 1); @@ -933,6 +926,51 @@ public class ModernCardRenderer extends CardRenderer { } } + public void paintOutlineTextByGlow(Graphics2D g, String text, Color color, int x, int y) { + GlowText label = new GlowText(); + label.setGlow(Color.black, 6, 3); + label.setText(text); + label.setFont(g.getFont().deriveFont(Font.BOLD)); + label.setForeground(color); + Dimension ptSize = label.getPreferredSize(); + label.setSize(ptSize.width, ptSize.height); + g.drawImage(label.getGlowImage(), x, y, null); + } + + public void paintOutlineTextByStroke(Graphics2D g, String text, Color color, int x, int y) { + // https://stackoverflow.com/a/35222059/1276632 + Color outlineColor = Color.black; + Color fillColor = color; + BasicStroke outlineStroke = new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + + // remember original settings + Color originalColor = g.getColor(); + Stroke originalStroke = g.getStroke(); + RenderingHints originalHints = g.getRenderingHints(); + + // create a glyph vector from your text + GlyphVector glyphVector = g.getFont().createGlyphVector(g.getFontRenderContext(), text); + // get the shape object + Shape textShape = glyphVector.getOutline(x, y); + + // activate anti aliasing for text rendering (if you want it to look nice) + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + g.setColor(outlineColor); + g.setStroke(outlineStroke); + g.draw(textShape); // draw outline + + g.setColor(fillColor); + g.fill(textShape); // fill the shape + + // reset to original settings after painting + g.setColor(originalColor); + g.setStroke(originalStroke); + g.setRenderingHints(originalHints); + } + // Draw the P/T and/or Loyalty boxes protected void drawBottomRight(Graphics2D g, Paint borderPaint, Color fill) { // No bottom right for abilities @@ -944,17 +982,31 @@ public class ModernCardRenderer extends CardRenderer { int curY = cardHeight - (int) (0.03f * cardHeight); // Width of the boxes - int partWidth = (int) Math.max(30, 0.20f * cardWidth); + int partBoxWidth = (int) Math.max(30, 0.20f * cardWidth); // Is it a creature? boolean isVehicle = cardView.getSubTypes().contains(SubType.VEHICLE); if (cardView.isCreature() || isVehicle) { - int x = cardWidth - borderWidth - partWidth; + + // draws p/t by parts + int ptDeviderSpace = 1; // Arial font is too narrow for devider (2/2) and needs extra space + String ptText1 = cardView.getPower(); + String ptText2 = "/"; + String ptText3 = CardRendererUtils.getCardLifeWithDamage(cardView); + int ptTextWidth1 = g.getFontMetrics(ptTextFont).stringWidth(ptText1); + int ptTextWidth2 = g.getFontMetrics(ptTextFont).stringWidth(ptText2) + 2 * ptDeviderSpace; + int ptTextWidth3 = g.getFontMetrics(ptTextFont).stringWidth(ptText3); + + // PT max size + int ptContentWidth = contentInset + ptTextWidth1 + ptDeviderSpace + ptTextWidth2 + ptDeviderSpace + ptTextWidth3 + contentInset; + partBoxWidth = Math.max(ptContentWidth, partBoxWidth); + + int x = cardWidth - borderWidth - partBoxWidth; // Draw PT box CardRendererUtils.drawRoundedBox(g, x, curY - boxHeight, - partWidth, boxHeight, + partBoxWidth, boxHeight, contentInset, borderPaint, isVehicle ? BOX_VEHICLE : fill); @@ -963,27 +1015,42 @@ public class ModernCardRenderer extends CardRenderer { g.setColor(new Color(0, 0, 0, 150)); g.fillRect( x + contentInset, curY - boxHeight - 1, - partWidth - 2 * contentInset, 1); + partBoxWidth - 2 * contentInset, 1); // Draw text - Color textColor; + Color defaultTextColor; + boolean defaultTextLight; if (isVehicle) { boolean isAnimated = !(cardView instanceof PermanentView) || cardView.isCreature(); if (isAnimated) { - textColor = Color.white; + defaultTextColor = Color.white; } else { - textColor = new Color(180, 180, 180); + defaultTextColor = new Color(180, 180, 180); } - + defaultTextLight = true; } else { - textColor = getBoxTextColor(); + defaultTextColor = getBoxTextColor(); + defaultTextLight = !defaultTextColor.equals(Color.black); } - g.setColor(textColor); + g.setColor(defaultTextColor); g.setFont(ptTextFont); - String ptText = cardView.getPower() + '/' + cardView.getToughness(); - int ptTextWidth = g.getFontMetrics().stringWidth(ptText); - g.drawString(ptText, - x + (partWidth - ptTextWidth) / 2, curY - ptTextOffset - 1); + + // draws + int ptEmptySpace = (partBoxWidth - ptContentWidth) / 2; + int ptPosStart1 = x + contentInset + ptEmptySpace; + int ptPosStart2 = ptPosStart1 + ptTextWidth1 + ptDeviderSpace; + int ptPosStart3 = ptPosStart2 + ptTextWidth2 + ptDeviderSpace; + // p + g.setColor(CardRendererUtils.getCardTextColor(cardView.getOriginalCard().getPower(), false, defaultTextColor, defaultTextLight)); + g.drawString(ptText1, ptPosStart1, curY - ptTextOffset - 1); // left + // / + g.setColor(defaultTextColor); + g.drawString(ptText2, ptPosStart2, curY - ptTextOffset - 1); // center + // t + g.setColor(CardRendererUtils.getCardTextColor(cardView.getOriginalCard().getPower(), CardRendererUtils.isCardWithDamage(cardView), defaultTextColor, defaultTextLight)); + g.drawString(ptText3, ptPosStart3, curY - ptTextOffset - 1); // right + // + g.setColor(defaultTextColor); // Advance curY -= boxHeight; @@ -994,9 +1061,9 @@ public class ModernCardRenderer extends CardRenderer { if (cardView.isPlanesWalker() && (cardView instanceof PermanentView || !cardView.getStartingLoyalty().equals("0"))) { // Draw the PW loyalty box - int w = partWidth; - int h = partWidth / 2; - int x = cardWidth - partWidth - borderWidth; + int w = partBoxWidth; + int h = partBoxWidth / 2; + int x = cardWidth - partBoxWidth - borderWidth; int y = curY - h; Polygon symbol = new Polygon( @@ -1047,16 +1114,16 @@ public class ModernCardRenderer extends CardRenderer { // does it have damage on it? if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { - int x = cardWidth - partWidth - borderWidth; + int x = cardWidth - partBoxWidth - borderWidth; int y = curY - boxHeight; String damage = String.valueOf(((PermanentView) cardView).getDamage()); g.setFont(ptTextFont); int txWidth = g.getFontMetrics().stringWidth(damage); g.setColor(Color.red); - g.fillRect(x, y, partWidth, boxHeight); + g.fillRect(x, y, partBoxWidth, boxHeight); g.setColor(Color.white); - g.drawRect(x, y, partWidth, boxHeight); - g.drawString(damage, x + (partWidth - txWidth) / 2, curY - 1); + g.drawRect(x, y, partBoxWidth, boxHeight); + g.drawString(damage, x + (partBoxWidth - txWidth) / 2, curY - 1); } } 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 4e690bf1a5..c43151d60a 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 @@ -2,7 +2,6 @@ package org.mage.plugins.card; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; -import mage.client.dialog.PreferencesDialog; import mage.client.util.GUISizeHelper; import mage.interfaces.plugin.CardPlugin; import mage.view.CardView; @@ -99,25 +98,28 @@ public class CardPluginImpl implements CardPlugin { * Temporary card rendering shim. Split card rendering isn't implemented * yet, so use old component based rendering for the split cards. */ - private CardPanel makePanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, boolean isFoil, Dimension dimension) { - String fallback = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_FALLBACK, "false"); - if (fallback.equals("true")) { - return new CardPanelComponentImpl(view, gameId, loadImage, callback, isFoil, dimension); - } else { - return new CardPanelRenderImpl(view, gameId, loadImage, callback, isFoil, dimension); + private CardPanel makePanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, boolean isFoil, Dimension dimension, int renderMode) { + switch (renderMode) { + case 0: + return new CardPanelRenderImpl(view, gameId, loadImage, callback, isFoil, dimension); + case 1: + return new CardPanelComponentImpl(view, gameId, loadImage, callback, isFoil, dimension); + default: + throw new IllegalStateException("Unknown render mode " + renderMode); + } } @Override - public MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) { - CardPanel cardPanel = makePanel(permanent, gameId, loadImage, callback, false, dimension); + public MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode) { + CardPanel cardPanel = makePanel(permanent, gameId, loadImage, callback, false, dimension, renderMode); cardPanel.setShowCastingCost(true); return cardPanel; } @Override - public MagePermanent getMageCard(CardView cardView, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage) { - CardPanel cardPanel = makePanel(cardView, gameId, loadImage, callback, false, dimension); + public MagePermanent getMageCard(CardView cardView, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode) { + CardPanel cardPanel = makePanel(cardView, gameId, loadImage, callback, false, dimension, renderMode); cardPanel.setShowCastingCost(true); return cardPanel; } 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 47cd7597f1..6be07dc523 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 @@ -50,8 +50,25 @@ public class ScryfallImageSupportTokens { put("RNA/Zombie", "https://api.scryfall.com/cards/trna/3/en?format=image"); // WAR - put("WAR/Zombie Army", "https://api.scryfall.com/cards/twar/8/en?format=image"); - + put("WAR/Angel", "https://api.scryfall.com/cards/twar/2/en?format=image"); + put("WAR/Assassin", "https://api.scryfall.com/cards/twar/6/en?format=image"); + put("WAR/Citizen", "https://api.scryfall.com/cards/twar/16/en?format=image"); + put("WAR/Devil", "https://api.scryfall.com/cards/twar/12/en?format=image"); + put("WAR/Dragon", "https://api.scryfall.com/cards/twar/13/en?format=image"); + put("WAR/Goblin", "https://api.scryfall.com/cards/twar/14/en?format=image"); + put("WAR/Emblem Nissa, Who Shakes the World", "https://api.scryfall.com/cards/twar/19/en?format=image"); + put("WAR/Servo", "https://api.scryfall.com/cards/twar/18/en?format=image"); + put("WAR/Soldier", "https://api.scryfall.com/cards/twar/3/en?format=image"); + put("WAR/Spirit", "https://api.scryfall.com/cards/twar/1/en?format=image"); + put("WAR/Voja, Friend to Elves", "https://api.scryfall.com/cards/twar/17/en?format=image"); + put("WAR/Wall", "https://api.scryfall.com/cards/twar/4/en?format=image"); + put("WAR/Wizard", "https://api.scryfall.com/cards/twar/5/en?format=image"); + put("WAR/Wolf", "https://api.scryfall.com/cards/twar/15/en?format=image"); + put("WAR/Zombie Army/1", "https://api.scryfall.com/cards/twar/10/en?format=image"); + put("WAR/Zombie Army/2", "https://api.scryfall.com/cards/twar/8/en?format=image"); + put("WAR/Zombie Army/3", "https://api.scryfall.com/cards/twar/9/en?format=image"); + put("WAR/Zombie Warrior", "https://api.scryfall.com/cards/twar/11/en?format=image"); + put("WAR/Zombie", "https://api.scryfall.com/cards/twar/7/en?format=image"); // generate supported sets supportedSets.clear(); 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 c7dae39355..a22e0667b3 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 @@ -52,7 +52,7 @@ public final class CardImageUtils { return filePath; } - log.warn("Token image file not found. Set: " + card.getSet() + " Token Set Code: " + card.getTokenSetCode() + " Name: " + card.getName() + " File path: " + getTokenImagePath(card)); + //log.warn("Token image file not found. Set: " + card.getSet() + " Token Set Code: " + card.getTokenSetCode() + " Name: " + card.getName() + " File path: " + getTokenImagePath(card)); } else { log.warn("Trying to get token path for non token card. Set: " + card.getSet() + " Set Code: " + card.getTokenSetCode() + " Name: " + card.getName()); } diff --git a/Mage.Client/src/main/resources/card-pictures-tok.txt b/Mage.Client/src/main/resources/card-pictures-tok.txt index 481ac3559d..98099ba71d 100644 --- a/Mage.Client/src/main/resources/card-pictures-tok.txt +++ b/Mage.Client/src/main/resources/card-pictures-tok.txt @@ -87,6 +87,7 @@ |Generate|EMBLEM:SWS|Obi-Wan Kenobi||Emblem Obi-Wan Kenobi|ObiWanKenobiEmblem| |Generate|EMBLEM:RIX|Huatli, Radiant Champion||Emblem Huatli|HuatliRadiantChampionEmblem| |Generate|EMBLEM:RNA|Domri, Chaos Bringer||Emblem Domri|DomriChaosBringerEmblem| +|Generate|EMBLEM:WAR|Nissa, Who Shakes the World||Emblem Nissa|NissaWhoShakesTheWorldEmblem| |Generate|PLANE:PCA|Plane - Academy At Tolaria West|||AcademyAtTolariaWestPlane| |Generate|PLANE:PCA|Plane - Agyrem|||AgyremPlane| |Generate|PLANE:PCA|Plane - Akoum|||AkoumPlane| @@ -1198,4 +1199,27 @@ |Generate|TOK:RNA|Thopter|||ThopterToken| |Generate|TOK:RNA|Treasure|||TreasureToken| |Generate|TOK:RNA|Zombie|||ZombieToken| -|Generate|TOK:WAR|Zombie Army|||ZombieArmyToken| \ No newline at end of file +|Generate|TOK:WAR|Angel|||AngelVigilanceToken| +|Generate|TOK:WAR|Assassin|||AssassinToken2| +|Generate|TOK:WAR|Devil|||DevilToken| +|Generate|TOK:WAR|Dragon|||DragonToken| +|Generate|TOK:WAR|Goblin|||GoblinToken| +|Generate|TOK:WAR|Servo|||ServoToken| +|Generate|TOK:WAR|Soldier|||SoldierVigilanceToken| +|Generate|TOK:WAR|Spirit|||UginTheIneffableToken| +|Generate|TOK:WAR|Voja, Friend to Elves|||VojaFriendToElvesToken| +|Generate|TOK:WAR|Wall|||TeyoToken| +|Generate|TOK:WAR|Wizard|||WizardToken| +|Generate|TOK:WAR|Wolf|||WolfToken| +|Generate|TOK:WAR|Zombie|||ZombieToken| +|Generate|TOK:WAR|Zombie Warrior|||GodEternalOketraToken| +|Generate|TOK:WAR|Zombie Army|1||ZombieArmyToken| +|Generate|TOK:WAR|Zombie Army|2||ZombieArmyToken| +|Generate|TOK:WAR|Zombie Army|3||ZombieArmyToken| + + + + + + + diff --git a/Mage.Client/src/main/resources/cardrender/beleren-bold.ttf b/Mage.Client/src/main/resources/cardrender/beleren-bold.ttf deleted file mode 100644 index 7dd1bff6a7..0000000000 Binary files a/Mage.Client/src/main/resources/cardrender/beleren-bold.ttf and /dev/null differ diff --git a/Mage.Client/src/test/java/mage/client/game/MultiConnectTest.java b/Mage.Client/src/test/java/mage/client/game/MultiConnectTest.java index 7db5992ad1..ddcc9b34a7 100644 --- a/Mage.Client/src/test/java/mage/client/game/MultiConnectTest.java +++ b/Mage.Client/src/test/java/mage/client/game/MultiConnectTest.java @@ -71,7 +71,7 @@ public class MultiConnectTest { } @Override - public void disconnected(boolean errorCall) { + public void disconnected(boolean askToReconnect) { logger.info("disconnected"); } diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index 4ffa9351b8..b82542869a 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-common diff --git a/Mage.Common/src/main/java/mage/interfaces/MageClient.java b/Mage.Common/src/main/java/mage/interfaces/MageClient.java index 09c079152f..e1612c3f72 100644 --- a/Mage.Common/src/main/java/mage/interfaces/MageClient.java +++ b/Mage.Common/src/main/java/mage/interfaces/MageClient.java @@ -1,11 +1,9 @@ - package mage.interfaces; import mage.interfaces.callback.CallbackClient; import mage.utils.MageVersion; /** - * * @author BetaSteward_at_googlemail.com */ public interface MageClient extends CallbackClient { @@ -14,7 +12,7 @@ public interface MageClient extends CallbackClient { void connected(String message); - void disconnected(boolean errorCall); + void disconnected(boolean askToReconnect); void showMessage(String message); diff --git a/Mage.Common/src/main/java/mage/interfaces/plugin/CardPlugin.java b/Mage.Common/src/main/java/mage/interfaces/plugin/CardPlugin.java index 7aab71f8b5..138c71073f 100644 --- a/Mage.Common/src/main/java/mage/interfaces/plugin/CardPlugin.java +++ b/Mage.Common/src/main/java/mage/interfaces/plugin/CardPlugin.java @@ -1,30 +1,28 @@ package mage.interfaces.plugin; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.Map; -import java.util.UUID; -import javax.swing.*; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; import mage.view.CardView; import mage.view.PermanentView; import net.xeoh.plugins.base.Plugin; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Map; +import java.util.UUID; + /** * Interface for card plugins * - * @version 0.6 17.07.2011 added options to #sortPermanents - * @version 0.3 21.11.2010 #getMageCard - * @version 0.2 07.11.2010 #downloadImages - * @version 0.1 31.10.2010 #getMagePermanent, #sortPermanents * @author nantuko + * @version 0.1 31.10.2010 #getMagePermanent, #sortPermanents */ public interface CardPlugin extends Plugin { - MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage); + MagePermanent getMagePermanent(PermanentView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode); - MagePermanent getMageCard(CardView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage); + MagePermanent getMageCard(CardView permanent, Dimension dimension, UUID gameId, ActionCallback callback, boolean canBeFoil, boolean loadImage, int renderMode); int sortPermanents(Map ui, Map cards, boolean nonPermanentsOwnRow, boolean topPanel); diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index 394f5c910f..71f982f69f 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -28,14 +28,16 @@ import org.jboss.remoting.transport.bisocket.Bisocket; import org.jboss.remoting.transport.socket.SocketWrapper; import org.jboss.remoting.transporter.TransporterClient; +import javax.swing.*; import java.io.*; import java.lang.reflect.UndeclaredThrowableException; import java.net.*; import java.util.*; +import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class SessionImpl implements Session { @@ -54,6 +56,7 @@ public class SessionImpl implements Session { private ServerState serverState; private SessionState sessionState = SessionState.DISCONNECTED; private Connection connection; + private RemotingTask lastRemotingTask = null; private static final int PING_CYCLES = 10; private final LinkedList pingTime = new LinkedList<>(); private String pingInfo = ""; @@ -76,22 +79,56 @@ public class SessionImpl implements Session { return sessionId; } - // RemotingTask encapsulates a task which is involved with some JBoss Remoting. This is - // intended to be used with handleRemotingTaskExceptions for sharing the common exception - // handling. - public interface RemotingTask { + // RemotingTask - do server side works in background and return result, can be canceled at any time + public abstract class RemotingTask { - boolean run() throws Throwable; + SwingWorker worker = null; + Throwable lastError = null; + + abstract public boolean work() throws Throwable; + + boolean doWork() throws Throwable { + worker = new SwingWorker() { + @Override + protected Boolean doInBackground() { + try { + return work(); + } catch (Throwable t) { + lastError = t; + return false; + } + } + }; + worker.execute(); + + boolean res = worker.get(); + if (lastError != null) { + throw lastError; + } + return res; + } + + public void cancel() { + if (worker != null) { + worker.cancel(true); + } + } } - // handleRemotingTaskExceptions runs the given task and handles exceptions appropriately. This - // way we can share the common exception handling. - private boolean handleRemotingTaskExceptions(RemotingTask remoting) { + private void showMessageToUser(String message) { + client.showMessage("Remote task error. " + message); + } + + private boolean doRemoteWorkAndHandleErrors(RemotingTask remoting) { + // execute remote task and wait result, can be canceled + lastRemotingTask = remoting; try { - return remoting.run(); + return remoting.doWork(); + } catch (InterruptedException | CancellationException t) { + // was canceled by user, nothing to show } catch (MalformedURLException ex) { - logger.fatal("", ex); - client.showMessage("Unable connect to server. " + ex.getMessage()); + logger.fatal("Connect: wrong server address", ex); + showMessageToUser(ex.getMessage()); } catch (UndeclaredThrowableException ex) { String addMessage = ""; Throwable cause = ex.getCause(); @@ -103,7 +140,7 @@ public class SessionImpl implements Session { addMessage = "Probably the server version is not compatible with the client. "; } } else { - logger.error("Unknown server error", exep.getCause()); + logger.error("Connect: unknown server error", exep.getCause()); } } else if (cause instanceof NoSuchMethodException) { // NoSuchMethodException is thrown on an invocation of an unknow JBoss remoting @@ -112,66 +149,59 @@ public class SessionImpl implements Session { + "server version is not compatible with the client: " + cause.getMessage(); } if (addMessage.isEmpty()) { - logger.fatal("", ex); + logger.fatal("Connect: unknown error", ex); } - client.showMessage("Unable connect to server. " + addMessage + (ex.getMessage() != null ? ex.getMessage() : "")); + showMessageToUser(addMessage + (ex.getMessage() != null ? ex.getMessage() : "")); } catch (IOException ex) { - logger.fatal("", ex); + logger.fatal("Connect: unknown IO error", ex); String addMessage = ""; if (ex.getMessage() != null && ex.getMessage().startsWith("Unable to perform invocation")) { addMessage = "Maybe the server version is not compatible. "; } - client.showMessage("Unable connect to server. " + addMessage + (ex.getMessage() != null ? ex.getMessage() : "")); + showMessageToUser(addMessage + (ex.getMessage() != null ? ex.getMessage() : "")); } catch (MageVersionException ex) { - if (!canceled) { - client.showMessage("Unable connect to server. " + ex.getMessage()); - } + logger.warn("Connect: wrong versions"); disconnect(false); + if (!canceled) { + showMessageToUser(ex.getMessage()); + } } catch (CannotConnectException ex) { if (!canceled) { handleCannotConnectException(ex); } } catch (Throwable t) { - logger.fatal("Unable connect to server - ", t); + logger.fatal("Connect: FAIL", t); + disconnect(false); if (!canceled) { - disconnect(false); - StringBuilder sb = new StringBuilder(); - sb.append("Unable connect to server.\n"); - for (StackTraceElement element : t.getStackTrace()) { - sb.append(element.toString()).append('\n'); - } - client.showMessage(sb.toString()); + showMessageToUser(t.getMessage()); } + } finally { + lastRemotingTask = null; } return false; } @Override public synchronized boolean register(final Connection connection) { - return establishJBossRemotingConnection(connection) && handleRemotingTaskExceptions(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { @Override - public boolean run() throws Throwable { - logger.info("Trying to register as " + getUserName() + " to XMAGE server at " + connection.getHost() + ':' + connection.getPort()); - boolean registerResult = server.registerUser(sessionId, connection.getUsername(), - connection.getPassword(), connection.getEmail()); - if (registerResult) { - logger.info("Registered as " + getUserName() + " to MAGE server at " + connection.getHost() + ':' + connection.getPort()); - } - return registerResult; + public boolean work() throws Throwable { + logger.info("Registration: username " + getUserName() + " for email " + getEmail()); + boolean result = server.registerUser(sessionId, connection.getUsername(), connection.getPassword(), connection.getEmail()); + logger.info("Registration: " + (result ? "DONE, check your email for new password" : "FAIL")); + return result; } }); } @Override public synchronized boolean emailAuthToken(final Connection connection) { - return establishJBossRemotingConnection(connection) && handleRemotingTaskExceptions(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { @Override - public boolean run() throws Throwable { - logger.info("Trying to ask for an auth token to " + getEmail() + " to XMAGE server at " + connection.getHost() + ':' + connection.getPort()); + public boolean work() throws Throwable { + logger.info("Auth request: requesting auth token for username " + getUserName() + " to email " + getEmail()); boolean result = server.emailAuthToken(sessionId, connection.getEmail()); - if (result) { - logger.info("An auth token is emailed to " + getEmail() + " from MAGE server at " + connection.getHost() + ':' + connection.getPort()); - } + logger.info("Auth request: " + (result ? "DONE, check your email for auth token" : "FAIL")); return result; } }); @@ -179,14 +209,12 @@ public class SessionImpl implements Session { @Override public synchronized boolean resetPassword(final Connection connection) { - return establishJBossRemotingConnection(connection) && handleRemotingTaskExceptions(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { @Override - public boolean run() throws Throwable { - logger.info("Trying reset the password in XMAGE server at " + connection.getHost() + ':' + connection.getPort()); + public boolean work() throws Throwable { + logger.info("Password reset: reseting password for username " + getUserName()); boolean result = server.resetPassword(sessionId, connection.getEmail(), connection.getAuthToken(), connection.getPassword()); - if (result) { - logger.info("Password is successfully reset in MAGE server at " + connection.getHost() + ':' + connection.getPort()); - } + logger.info("Password reset: " + (result ? "DONE, check your email for new password" : "FAIL")); return result; } }); @@ -194,39 +222,39 @@ public class SessionImpl implements Session { @Override public synchronized boolean connect(final Connection connection) { - return establishJBossRemotingConnection(connection) - && handleRemotingTaskExceptions(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { @Override - public boolean run() throws Throwable { + public boolean work() throws Throwable { setLastError(""); - logger.info("Trying to log-in as " + getUserName() + " to XMAGE server at " + connection.getHost() + ':' + connection.getPort()); - boolean registerResult; + logger.info("Logging: as username " + getUserName() + " to server " + connection.getHost() + ':' + connection.getPort()); + boolean result; + if (connection.getAdminPassword() == null) { // for backward compatibility. don't remove twice call - first one does nothing but for version checking - registerResult = server.connectUser(connection.getUsername(), connection.getPassword(), sessionId, client.getVersion(), connection.getUserIdStr()); + result = server.connectUser(connection.getUsername(), connection.getPassword(), sessionId, client.getVersion(), connection.getUserIdStr()); } else { - registerResult = server.connectAdmin(connection.getAdminPassword(), sessionId, client.getVersion()); + result = server.connectAdmin(connection.getAdminPassword(), sessionId, client.getVersion()); } - if (registerResult) { + + if (result) { serverState = server.getServerState(); // client side check for incompatible versions if (client.getVersion().compareTo(serverState.getVersion()) != 0) { - String err = "Client and server versions are incompatible."; - setLastError(err); - logger.info(err); - disconnect(false); - return false; + throw new MageVersionException(client.getVersion(), serverState.getVersion()); } if (!connection.getUsername().equals("Admin")) { server.setUserData(connection.getUsername(), sessionId, connection.getUserData(), client.getVersion().toString(), connection.getUserIdStr()); updateDatabase(connection.isForceDBComparison(), serverState); } - logger.info("Logged-in as " + getUserName() + " to MAGE server at " + connection.getHost() + ':' + connection.getPort()); + + logger.info("Logging: DONE"); client.connected(getUserName() + '@' + connection.getHost() + ':' + connection.getPort() + ' '); return true; } + + logger.info("Logging: FAIL"); disconnect(false); return false; } @@ -241,20 +269,24 @@ public class SessionImpl implements Session { @Override public boolean stopConnecting() { canceled = true; + if (lastRemotingTask != null) { + lastRemotingTask.cancel(); + } return true; } - private boolean establishJBossRemotingConnection(final Connection connection) { + private boolean doRemoteConnection(final Connection connection) { + // connect to server and setup all data, can be canceled if (isConnected()) { disconnect(true); } this.connection = connection; this.canceled = false; sessionState = SessionState.CONNECTING; - boolean result = handleRemotingTaskExceptions(new RemotingTask() { + lastRemotingTask = new RemotingTask() { @Override - public boolean run() throws Throwable { - logger.info("Trying to connect to XMAGE server at " + connection.getHost() + ':' + connection.getPort()); + public boolean work() throws Throwable { + logger.info("Connect: connecting to server " + connection.getHost() + ':' + connection.getPort()); System.setProperty("http.nonProxyHosts", "code.google.com"); System.setProperty("socksNonProxyHosts", "code.google.com"); @@ -265,6 +297,9 @@ public class SessionImpl implements Session { System.clearProperty("http.proxyHost"); System.clearProperty("http.proxyPort"); + if (connection.getProxyType() != Connection.ProxyType.NONE) { + logger.info("Connect: using proxy " + connection.getProxyHost() + ":" + connection.getProxyPort()); + } switch (connection.getProxyType()) { case SOCKS: System.setProperty("socksProxyHost", connection.getProxyHost()); @@ -392,15 +427,24 @@ public class SessionImpl implements Session { sessionId = callbackClient.getSessionId(); sessionState = SessionState.CONNECTED; - logger.info("Connected to MAGE server at " + connection.getHost() + ':' + connection.getPort()); + logger.info("Connect: DONE"); return true; } - }); + }; + + boolean result; + try { + result = doRemoteWorkAndHandleErrors(lastRemotingTask); + } finally { + lastRemotingTask = null; + } + if (result) { return true; + } else { + disconnect(false); + return false; } - disconnect(false); - return false; } private void updateDatabase(boolean forceDBComparison, ServerState serverState) { @@ -463,7 +507,7 @@ public class SessionImpl implements Session { @Override public synchronized void disconnect(boolean askForReconnect) { if (isConnected()) { - logger.info("DISCONNECT (still connected)"); + logger.info("Disconnecting..."); sessionState = SessionState.DISCONNECTING; } if (connection == null || sessionState == SessionState.DISCONNECTED) { @@ -471,18 +515,20 @@ public class SessionImpl implements Session { } try { - callbackClient.removeListener(callbackHandler); - callbackClient.disconnect(); + if (callbackClient.isConnected()) { + callbackClient.removeListener(callbackHandler); + callbackClient.disconnect(); + } TransporterClient.destroyTransporterClient(server); } catch (Throwable ex) { - logger.fatal("Error disconnecting ...", ex); + logger.fatal("Disconnecting FAIL", ex); } if (sessionState == SessionState.DISCONNECTING || sessionState == SessionState.CONNECTING) { sessionState = SessionState.DISCONNECTED; - logger.info("Disconnected ... "); + logger.info("Disconnecting DONE"); if (askForReconnect) { - client.showError("Network error. You have been disconnected from " + connection.getHost()); + client.showError("Network error. You have been disconnected from " + connection.getHost()); } client.disconnected(askForReconnect); // MageFrame with check to reconnect pingTime.clear(); @@ -491,7 +537,6 @@ public class SessionImpl implements Session { @Override public synchronized void reconnect(Throwable throwable) { - logger.info("RECONNECT - Connected: " + isConnected()); client.disconnected(true); } @@ -522,7 +567,7 @@ public class SessionImpl implements Session { @Override public void handleConnectionException(Throwable throwable, Client client) { - logger.info("connection to server lost - " + throwable.getMessage(), throwable); + logger.info("Connect: lost connection to server.", throwable); reconnect(throwable); } } @@ -1538,14 +1583,24 @@ public class SessionImpl implements Session { } private void handleThrowable(Throwable t) { - logger.fatal("Communication error", t); + + // ignore interrupted exceptions -- it's connection problem or user's close if (t instanceof InterruptedException) { - logger.error("Was interrupted", new Throwable()); + //logger.error("Connection error: was interrupted", t); + Thread.currentThread().interrupt(); + return; } - // Probably this can cause hanging the client under certain circumstances as the disconnect method is synchronized - // so check if it's needed - // disconnect(true); + if (t instanceof RuntimeException) { + RuntimeException re = (RuntimeException) t; + if (t.getCause() instanceof InterruptedException) { + //logger.error("Connection error: was interrupted by runtime exception", t.getCause()); + Thread.currentThread().interrupt(); + return; + } + } + + logger.fatal("Connection error: other", t); } private void handleMageException(MageException ex) { @@ -1592,7 +1647,7 @@ public class SessionImpl implements Session { @Override public boolean ping() { try { - if (isConnected()) { + if (isConnected() && sessionId != null) { long startTime = System.nanoTime(); if (!server.ping(sessionId, pingInfo)) { logger.error("Ping failed: " + this.getUserName() + " Session: " + sessionId + " to MAGE server at " + connection.getHost() + ':' + connection.getPort()); diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 7650fed11c..858c0dc6eb 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -11,9 +11,9 @@ public class MageVersion implements Serializable, Comparable { public static final int MAGE_VERSION_MAJOR = 1; public static final int MAGE_VERSION_MINOR = 4; - public static final int MAGE_VERSION_PATCH = 34; + public static final int MAGE_VERSION_PATCH = 35; public static final String MAGE_EDITION_INFO = ""; // set "-beta" for 1.4.32-betaV0 - public static final String MAGE_VERSION_MINOR_PATCH = "V0.2"; // default + public static final String MAGE_VERSION_MINOR_PATCH = "V5"; // default // strict mode private static final boolean MAGE_VERSION_MINOR_PATCH_MUST_BE_SAME = true; // set true on uncompatible github changes, set false after new major release (after MAGE_VERSION_PATCH changes) diff --git a/Mage.Plugins/Mage.Counter.Plugin/pom.xml b/Mage.Plugins/Mage.Counter.Plugin/pom.xml index f7b3e8b345..576fd8497d 100644 --- a/Mage.Plugins/Mage.Counter.Plugin/pom.xml +++ b/Mage.Plugins/Mage.Counter.Plugin/pom.xml @@ -7,7 +7,7 @@ org.mage mage-plugins - 1.4.34 + 1.4.35 mage-counter-plugin diff --git a/Mage.Plugins/pom.xml b/Mage.Plugins/pom.xml index 92279b2885..98b9b80e55 100644 --- a/Mage.Plugins/pom.xml +++ b/Mage.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-plugins diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index 32f8708883..ac1e109629 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage.server.console diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java index 33033b39be..1ee9285c39 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java @@ -174,7 +174,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { /** * @param args the command line arguments */ - public static void main(String args[]) { + public static void main(String[] args) { logger.info("Starting MAGE server console version " + version); logger.info("Logging level: " + logger.getEffectiveLevel()); @@ -210,7 +210,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { } @Override - public void disconnected(boolean errorCall) { + public void disconnected(boolean askToReconnect) { if (SwingUtilities.isEventDispatchThread()) { consolePanel1.stop(); setStatusText("Not connected"); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml index a65a27404e..9aba1692b5 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-deck-constructed diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java index 0c9cce272f..5e2ce9b0d2 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java @@ -60,7 +60,7 @@ public class FreeformCommander extends Constructed { for (Map.Entry entry : counts.entrySet()) { if (entry.getValue() > 1) { - if (!basicLandNames.contains(entry.getKey())) { + if (!basicLandNames.contains(entry.getKey()) && !anyNumberCardsAllowed.contains(entry.getKey())) { invalid.put(entry.getKey(), "Too many: " + entry.getValue()); valid = false; } diff --git a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml index 2c062e53da..b791930d78 100644 --- a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-deck-limited diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml index b6b58796d1..166ba661c8 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-brawlduel diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml index 29d4a729d4..524a4c0311 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-brawlfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml index e3e5dc6337..a0d09a5690 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-canadianhighlanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml index 98e3c4cc3f..5a3fc823de 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-commanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml index 6694de83d6..5758e3ff5e 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-commanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml index a121cfb253..079718579d 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-freeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml new file mode 100644 index 0000000000..96f60f12a1 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + + org.mage + mage-server-plugins + 1.4.35 + + + mage-game-freeformcommanderduel + jar + Mage Game Freeform Commander Two Player + + + + ${project.groupId} + mage + ${project.version} + + + + + src + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + maven-resources-plugin + + UTF-8 + + + + + + mage-game-freeformcommanderduel + + + + + diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuel.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuel.java new file mode 100644 index 0000000000..d57f520f07 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuel.java @@ -0,0 +1,36 @@ +package mage.game; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; + +/** + * @author JayDi85 + */ +public class FreeformCommanderDuel extends GameCommanderImpl { + + public FreeformCommanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); + } + + public FreeformCommanderDuel(final FreeformCommanderDuel game) { + super(game); + } + + @Override + public MatchType getGameType() { + return new FreeformCommanderDuelType(); + } + + @Override + public int getNumPlayers() { + return 2; + } + + @Override + public FreeformCommanderDuel copy() { + return new FreeformCommanderDuel(this); + } + +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelMatch.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelMatch.java new file mode 100644 index 0000000000..1f3e141929 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelMatch.java @@ -0,0 +1,31 @@ +package mage.game; + +import mage.game.match.MatchImpl; +import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; + +/** + * @author JayDi85 + */ +public class FreeformCommanderDuelMatch extends MatchImpl { + + public FreeformCommanderDuelMatch(MatchOptions options) { + super(options); + } + + @Override + public void startGame() throws GameException { + int startLife = 20; + boolean alsoHand = true; + boolean checkCommanderDamage = true; + + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + FreeformCommanderDuel game = new FreeformCommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); + game.setCheckCommanderDamage(checkCommanderDamage); + game.setStartMessage(this.createGameStartMessage()); + game.setAlsoHand(alsoHand); + game.setAlsoLibrary(true); + initGame(game); + games.add(game); + } +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelType.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelType.java new file mode 100644 index 0000000000..586617ee51 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/src/mage/game/FreeformCommanderDuelType.java @@ -0,0 +1,29 @@ +package mage.game; + +import mage.game.match.MatchType; + +/** + * @author JayDi85 + */ +public class FreeformCommanderDuelType extends MatchType { + + public FreeformCommanderDuelType() { + this.name = "Freeform Commander Two Player Duel"; + this.maxPlayers = 2; + this.minPlayers = 2; + this.numTeams = 0; + this.useAttackOption = false; + this.useRange = false; + this.sideboardingAllowed = false; + } + + protected FreeformCommanderDuelType(final FreeformCommanderDuelType matchType) { + super(matchType); + } + + @Override + public FreeformCommanderDuelType copy() { + return new FreeformCommanderDuelType(this); + } + +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml index 83c6bebe17..7755e2fec4 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-freeformcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml index febe0cd297..80676f0433 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-momirduel diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml index 296093b069..03eba8da4f 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-momirfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml index 4b36fb9a31..2b5cb442a6 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-pennydreadfulcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml index a2f8bc45de..f50fbebfaf 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-tinyleadersduel diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml index 2684ade9ae..0cc888e958 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-game-twoplayerduel diff --git a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml index 47835579ae..ff76ee638c 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-ai-draftbot diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml index 3b2b8d9f64..85cef7803d 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-ai-ma diff --git a/Mage.Server.Plugins/Mage.Player.AI/pom.xml b/Mage.Server.Plugins/Mage.Player.AI/pom.xml index 5dd8022286..5e79f6d396 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-ai 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 f6934d8ee8..3e6603d7b9 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 @@ -150,6 +150,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetPlayer) { return setTargetPlayer(outcome, target, null, sourceId, abilityControllerId, randomOpponentId, game); } + if (target.getOriginalTarget() instanceof TargetDiscard) { findPlayables(game); if (!unplayable.isEmpty()) { @@ -174,38 +175,43 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetControlledPermanent) { List targets; - targets = threats(abilityControllerId, sourceId, ((TargetControlledPermanent) target).getFilter(), game, target.getTargets()); + TargetControlledPermanent origTarget = (TargetControlledPermanent) target.getOriginalTarget(); + targets = threats(abilityControllerId, sourceId, origTarget.getFilter(), game, target.getTargets()); if (!outcome.isGood()) { Collections.reverse(targets); } for (Permanent permanent : targets) { - if (((TargetControlledPermanent) target).canTarget(abilityControllerId, permanent.getId(), sourceId, game, false) && !target.getTargets().contains(permanent.getId())) { + if (origTarget.canTarget(abilityControllerId, permanent.getId(), sourceId, game, false) && !target.getTargets().contains(permanent.getId())) { target.add(permanent.getId(), game); return true; } } return false; } + if (target.getOriginalTarget() instanceof TargetPermanent) { + TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget(); List targets; if (outcome.isCanTargetAll()) { - targets = threats(null, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(null, sourceId, origTarget.getFilter(), game, target.getTargets()); } else { if (outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, origTarget.getFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, origTarget.getFilter(), game, target.getTargets()); } if (targets.isEmpty() && target.isRequired()) { if (!outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, origTarget.getFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, origTarget.getFilter(), game, target.getTargets()); } } } + for (Permanent permanent : targets) { if (target.canTarget(abilityControllerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) { // stop to add targets if not needed and outcome is no advantage for AI player @@ -246,17 +252,18 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return target.isChosen(); } + if (target.getOriginalTarget() instanceof TargetAnyTarget) { List targets; - TargetAnyTarget t = ((TargetAnyTarget) target); + TargetAnyTarget origTarget = (TargetAnyTarget) target.getOriginalTarget(); if (outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { List alreadyTargetted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), null, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), null, game)) { if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) { target.add(permanent.getId(), game); return true; @@ -276,17 +283,18 @@ public class ComputerPlayer extends PlayerImpl implements Player { return false; } } + if (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) { List targets; - TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer) target); + TargetCreatureOrPlayer origTarget = (TargetCreatureOrPlayer) target.getOriginalTarget(); if (outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), null, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), null, game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { target.add(permanent.getId(), game); return true; @@ -309,9 +317,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { List targets; - TargetPermanentOrPlayer t = ((TargetPermanentOrPlayer) target); - List ownedTargets = threats(abilityControllerId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); - List opponentTargets = threats(randomOpponentId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); + TargetPermanentOrPlayer origTarget = (TargetPermanentOrPlayer) target.getOriginalTarget(); + List ownedTargets = threats(abilityControllerId, sourceId, ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); + List opponentTargets = threats(randomOpponentId, sourceId, ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); if (outcome.isGood()) { targets = ownedTargets; } else { @@ -319,7 +327,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(permanent.getId(), game)) { + if (target.canTarget(permanent.getId(), game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { target.add(permanent.getId(), game); return true; @@ -353,7 +361,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(permanent.getId(), game)) { + if (target.canTarget(permanent.getId(), game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { target.add(permanent.getId(), game); return true; @@ -362,6 +370,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetCardInGraveyard) { List cards = new ArrayList<>(); for (Player player : game.getPlayers().values()) { @@ -395,15 +404,15 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetSource) { Set targets; - TargetSource t = ((TargetSource) target); - targets = t.possibleTargets(sourceId, abilityControllerId, game); + targets = target.possibleTargets(sourceId, abilityControllerId, game); for (UUID targetId : targets) { MageObject targetObject = game.getObject(targetId); if (targetObject != null) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(targetObject.getId(), game)) { + if (target.canTarget(targetObject.getId(), game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(targetObject.getId())) { target.add(targetObject.getId(), game); return true; @@ -444,8 +453,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { return setTargetPlayer(outcome, target, source, source.getSourceId(), abilityControllerId, randomOpponentId, game); } - if (target.getOriginalTarget() instanceof TargetDiscard || target.getOriginalTarget() instanceof TargetCardInHand) { + if (target.getOriginalTarget() instanceof TargetDiscard + || target.getOriginalTarget() instanceof TargetCardInHand) { if (outcome.isGood()) { + // good Cards cards = new CardsImpl(target.possibleTargets(source.getSourceId(), getId(), game)); ArrayList cardsInHand = new ArrayList<>(cards.getCards(game)); while (!target.isChosen() @@ -463,6 +474,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } else { + // bad findPlayables(game); if (!unplayable.isEmpty()) { for (int i = unplayable.size() - 1; i >= 0; i--) { @@ -487,9 +499,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetControlledPermanent) { + TargetControlledPermanent origTarget = (TargetControlledPermanent) target.getOriginalTarget(); List targets; - targets = threats(abilityControllerId, source.getSourceId(), ((TargetControlledPermanent) target).getFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source.getSourceId(), origTarget.getFilter(), game, target.getTargets()); if (!outcome.isGood()) { Collections.reverse(targets); } @@ -504,9 +518,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { return target.isChosen(); } + if (target.getOriginalTarget() instanceof TargetPermanent) { List targets; - TargetPermanent t = (TargetPermanent) target.getOriginalTarget(); + TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget(); boolean outcomeTargets = true; if (outcome.isGood()) { targets = threats(abilityControllerId, source == null ? null : source.getSourceId(), ((TargetPermanent) target).getFilter(), game, target.getTargets()); @@ -520,7 +535,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { //targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game); } if (targets.isEmpty() && target.isRequired()) { - targets = game.getBattlefield().getActivePermanents(t.getFilter(), playerId, game); + targets = game.getBattlefield().getActivePermanents(origTarget.getFilter(), playerId, game); } for (Permanent permanent : targets) { if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { @@ -532,13 +547,14 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return target.isChosen(); } + if (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) { List targets; - TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer) target); + TargetCreatureOrPlayer origTarget = ((TargetCreatureOrPlayer) target); if (outcome.isGood()) { - targets = threats(abilityControllerId, source.getSourceId(), ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source.getSourceId(), ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source.getSourceId(), ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source.getSourceId(), ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } if (targets.isEmpty()) { @@ -552,11 +568,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (targets.isEmpty() && target.isRequired(source)) { - targets = game.getBattlefield().getActivePermanents(((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), playerId, game); + targets = game.getBattlefield().getActivePermanents(((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), playerId, game); } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { return tryAddTarget(target, permanent.getId(), source, game); } @@ -574,13 +590,14 @@ public class ComputerPlayer extends PlayerImpl implements Player { //if (!target.isRequired()) return false; } + if (target.getOriginalTarget() instanceof TargetAnyTarget) { List targets; - TargetAnyTarget t = ((TargetAnyTarget) target); + TargetAnyTarget origTarget = ((TargetAnyTarget) target); if (outcome.isGood()) { - targets = threats(abilityControllerId, source.getSourceId(), ((FilterCreaturePlayerOrPlaneswalker) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source.getSourceId(), ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source.getSourceId(), ((FilterCreaturePlayerOrPlaneswalker) t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source.getSourceId(), ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } if (targets.isEmpty()) { @@ -594,11 +611,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (targets.isEmpty() && target.isRequired(source)) { - targets = game.getBattlefield().getActivePermanents(((FilterCreaturePlayerOrPlaneswalker) t.getFilter()).getCreatureFilter(), playerId, game); + targets = game.getBattlefield().getActivePermanents(((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), playerId, game); } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { tryAddTarget(target, permanent.getId(), source, game); } @@ -616,13 +633,14 @@ public class ComputerPlayer extends PlayerImpl implements Player { //if (!target.isRequired()) return false; } + if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { List targets; - TargetPermanentOrPlayer t = ((TargetPermanentOrPlayer) target); + TargetPermanentOrPlayer origTarget = ((TargetPermanentOrPlayer) target); if (outcome.isGood()) { - targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source.getSourceId(), ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source.getSourceId(), ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } if (targets.isEmpty()) { @@ -636,11 +654,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (targets.isEmpty() && target.isRequired(source)) { - targets = game.getBattlefield().getActivePermanents(((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), playerId, game); + targets = game.getBattlefield().getActivePermanents(((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), playerId, game); } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { return tryAddTarget(target, permanent.getId(), source, game); } @@ -650,13 +668,27 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetPlayerOrPlaneswalker) { List targets; - TargetPlayerOrPlaneswalker t = ((TargetPlayerOrPlaneswalker) target); + TargetPlayerOrPlaneswalker origTarget = ((TargetPlayerOrPlaneswalker) target); + + // TODO: if effect is bad and no opponent's targets available then AI can't target yourself but must by rules + /* + battlefield:Computer:Mountain:5 + hand:Computer:Viashino Pyromancer:3 + battlefield:Human:Shalai, Voice of Plenty:1 + */ + // TODO: in multiplayer game there many opponents - if random opponents don't have targets then AI must use next opponent, but it skips + // (e.g. you randomOpponentId must be replaced by List randomOpponents) + + // normal cycle (good for you, bad for opponents) + + // possible good/bad permanents if (outcome.isGood()) { - targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) target.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source.getSourceId(), ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source.getSourceId(), ((FilterPermanentOrPlayer) target.getFilter()).getPermanentFilter(), game, target.getTargets()); } + // possible good/bad players if (targets.isEmpty()) { if (outcome.isGood()) { if (target.canTarget(getId(), abilityControllerId, source, game)) { @@ -667,18 +699,22 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } + // can't find targets (e.g. effect is bad, but you need take targets from yourself) if (targets.isEmpty() && target.isRequired(source)) { - targets = game.getBattlefield().getActivePermanents(((TargetPlayerOrPlaneswalker) t.getFilter()).getFilterPermanent(), playerId, game); + targets = game.getBattlefield().getActivePermanents(origTarget.getFilterPermanent(), playerId, game); } + + // try target permanent for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (t.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { return tryAddTarget(target, permanent.getId(), source, game); } } } + // try target player as normal if (outcome.isGood()) { if (target.canTarget(getId(), abilityControllerId, source, game)) { return tryAddTarget(target, abilityControllerId, source, game); @@ -703,6 +739,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { //if (!target.isRequired()) return false; } + if (target.getOriginalTarget() instanceof TargetCardInLibrary) { List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getLibrary().getCards(game)); Card card = pickTarget(cards, outcome, target, source, game); @@ -711,6 +748,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) { List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game)); while (!target.isChosen() && !cards.isEmpty()) { @@ -722,6 +760,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return target.isChosen(); } + if (target.getOriginalTarget() instanceof TargetSpell) { if (!game.getStack().isEmpty()) { for (StackObject o : game.getStack()) { @@ -732,17 +771,19 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetSpellOrPermanent) { // TODO: Also check if a spell should be selected + TargetSpellOrPermanent origTarget = (TargetSpellOrPermanent) target.getOriginalTarget(); List targets; boolean outcomeTargets = true; if (outcome.isGood()) { - targets = threats(abilityControllerId, source == null ? null : source.getSourceId(), ((TargetSpellOrPermanent) target).getPermanentFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(randomOpponentId, source == null ? null : source.getSourceId(), ((TargetSpellOrPermanent) target).getPermanentFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets()); } if (targets.isEmpty() && target.isRequired(source)) { - targets = threats(null, source == null ? null : source.getSourceId(), ((TargetSpellOrPermanent) target).getPermanentFilter(), game, target.getTargets()); + targets = threats(null, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets()); Collections.reverse(targets); outcomeTargets = false; } @@ -765,6 +806,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) { List cards = new ArrayList<>(); for (UUID uuid : game.getOpponents(abilityControllerId)) { @@ -780,6 +822,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { //if (!target.isRequired()) return false; } + if (target.getOriginalTarget() instanceof TargetDefender) { // TODO: Improve, now planeswalker is always chosen if it exits List targets; @@ -834,6 +877,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return target.isChosen(); } + if (target.getOriginalTarget() instanceof TargetActivatedAbility) { List stackObjects = new ArrayList<>(); for (UUID uuid : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) { @@ -924,6 +968,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } return false; } + if (target.getOriginalTarget() instanceof TargetCreatureOrPlaneswalkerAmount) { List targets; if (outcome.isGood()) { @@ -965,6 +1010,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } + log.warn("No proper AI target handling: " + target.getClass().getName()); return false; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml index 8f5fb17877..1df762162d 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-ai-mcts diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml index b142e1fd63..ae10a64f82 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-aiminimax diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index faafd88069..83cb9d5fdb 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-player-human diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 2323f9f6fd..b03c0cc907 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -934,24 +934,27 @@ public class HumanPlayer extends PlayerImpl { if (object != null) { Zone zone = game.getState().getZone(object.getId()); if (zone != null) { + // look at card or try to cast/activate abilities + Player actingPlayer = null; + LinkedHashMap useableAbilities = null; + if (playerId.equals(game.getPriorityPlayerId())) { + actingPlayer = this; + } else if (getPlayersUnderYourControl().contains(game.getPriorityPlayerId())) { + actingPlayer = game.getPlayer(game.getPriorityPlayerId()); + } + if (actingPlayer != null) { + useableAbilities = actingPlayer.getUseableActivatedAbilities(object, zone, game); + } + if (object instanceof Card && ((Card) object).isFaceDown(game) - && lookAtFaceDownCard((Card) object, game)) { + && lookAtFaceDownCard((Card) object, game, useableAbilities == null ? 0 : useableAbilities.size())) { result = true; } else { - Player actingPlayer = null; - if (playerId.equals(game.getPriorityPlayerId())) { - actingPlayer = this; - } else if (getPlayersUnderYourControl().contains(game.getPriorityPlayerId())) { - actingPlayer = game.getPlayer(game.getPriorityPlayerId()); - } - if (actingPlayer != null) { - LinkedHashMap useableAbilities = actingPlayer.getUseableActivatedAbilities(object, zone, game); - if (useableAbilities != null - && !useableAbilities.isEmpty()) { - activateAbility(useableAbilities, object, game); - result = true; - } + if (useableAbilities != null + && !useableAbilities.isEmpty()) { + activateAbility(useableAbilities, object, game); + result = true; } } } diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml index 88d4bcd908..e3acf90a53 100644 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-tournament-boosterdraft diff --git a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml index 4e04eaa458..9e3f659152 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-tournament-constructed diff --git a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml index 9c09235f0f..d359dd49a3 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.34 + 1.4.35 mage-tournament-sealed diff --git a/Mage.Server.Plugins/pom.xml b/Mage.Server.Plugins/pom.xml index 50ab453a49..248976e212 100644 --- a/Mage.Server.Plugins/pom.xml +++ b/Mage.Server.Plugins/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-server-plugins @@ -25,8 +25,9 @@ Mage.Game.TinyLeadersDuel Mage.Game.CanadianHighlanderDuel Mage.Game.PennyDreadfulCommanderFreeForAll - Mage.Game.FreeformCommanderFreeForAll - Mage.Game.BrawlDuel + Mage.Game.FreeformCommanderDuel + Mage.Game.FreeformCommanderFreeForAll + Mage.Game.BrawlDuel Mage.Game.BrawlFreeForAll Mage.Game.TwoPlayerDuel Mage.Player.AI diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index 0475c3cbe6..aa2223ceab 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -79,6 +79,7 @@ + diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index f5d4a5c71e..72d63a77d6 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-server @@ -184,6 +184,12 @@ ${project.version} runtime + + ${project.groupId} + mage-game-freeformcommanderduel + ${project.version} + runtime + ${project.groupId} diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index 2a294b3093..bea0cd3d65 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -73,6 +73,7 @@ + diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 030858f0ce..21ca15d278 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -1,11 +1,5 @@ - package mage.server; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import mage.server.User.UserState; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; @@ -13,6 +7,12 @@ import mage.server.util.ThreadExecutor; import mage.view.UserView; import org.apache.log4j.Logger; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + /** * manages users - if a user is disconnected and 10 minutes have passed with no * activity the user is removed @@ -22,6 +22,9 @@ import org.apache.log4j.Logger; public enum UserManager { instance; + private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status) + private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list + private static final Logger logger = Logger.getLogger(UserManager.class); protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); @@ -58,7 +61,7 @@ public enum UserManager { public Optional getUser(UUID userId) { if (!users.containsKey(userId)) { - logger.warn(String.format("User with id %s could not be found", userId), new Throwable()); // TODO: remove after session freezes fixed + //logger.warn(String.format("User with id %s could not be found", userId), new Throwable()); // TODO: remove after session freezes fixed return Optional.empty(); } else { return Optional.of(users.get(userId)); @@ -127,17 +130,17 @@ public enum UserManager { if (userId != null) { getUser(userId).ifPresent(user -> USER_EXECUTOR.execute( - () -> { - try { - logger.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId + " [" + user.getGameInfo() + ']'); - user.removeUserFromAllTables(reason); - ChatManager.instance.removeUser(user.getId(), reason); - logger.debug("USER REMOVE END - " + user.getName()); - } catch (Exception ex) { - handleException(ex); - } - } - )); + () -> { + try { + logger.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId + " [" + user.getGameInfo() + ']'); + user.removeUserFromAllTables(reason); + ChatManager.instance.removeUser(user.getId(), reason); + logger.debug("USER REMOVE END - " + user.getName()); + } catch (Exception ex) { + handleException(ex); + } + } + )); } } @@ -155,16 +158,15 @@ public enum UserManager { /** * Is the connection lost for more than 3 minutes, the user will be set to - * offline status. The user will be removed in validity check after 15 + * offline status. The user will be removed in validity check after 8 * minutes of no activities - * */ private void checkExpired() { try { Calendar calendarExp = Calendar.getInstance(); - calendarExp.add(Calendar.MINUTE, -3); + calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS); Calendar calendarRemove = Calendar.getInstance(); - calendarRemove.add(Calendar.MINUTE, -8); + calendarRemove.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS); List toRemove = new ArrayList<>(); logger.debug("Start Check Expired"); ArrayList userList = new ArrayList<>(); @@ -179,18 +181,18 @@ public enum UserManager { try { if (user.getUserState() == UserState.Offline) { if (user.isExpired(calendarRemove.getTime())) { + // removes from users list toRemove.add(user); } } else { if (user.isExpired(calendarExp.getTime())) { + // set disconnected status and removes from all activities (tourney/tables/games/drafts/chats) if (user.getUserState() == UserState.Connected) { user.lostConnection(); disconnect(user.getId(), DisconnectReason.BecameInactive); } removeUserFromAllTablesAndChat(user.getId(), DisconnectReason.SessionExpired); user.setUserState(UserState.Offline); - // Remove the user from all tournaments - } } } catch (Exception ex) { @@ -215,7 +217,6 @@ public enum UserManager { /** * This method recreated the user list that will be send to all clients - * */ private void updateUserInfoList() { try { 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 b0071b206d..8ec068a48e 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -49,6 +49,9 @@ import java.util.zip.GZIPOutputStream; */ public class GameController implements GameCallback { + private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status + private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 4 * 60; // leave player from game if it don't join and inactive on server + private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor(); private static final Logger logger = Logger.getLogger(GameController.class); @@ -226,7 +229,7 @@ public class GameController implements GameCallback { } catch (Exception ex) { logger.fatal("Send info about player not joined yet:", ex); } - }, 15, 15, TimeUnit.SECONDS); + }, GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS, GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS, TimeUnit.SECONDS); checkStart(); } @@ -320,6 +323,7 @@ public class GameController implements GameCallback { } private void sendInfoAboutPlayersNotJoinedYet() { + // runs every 15 secs untill all players join for (Player player : game.getPlayers().values()) { if (!player.hasLeft() && player.isHuman()) { Optional requestedUser = getUserByPlayerId(player.getId()); @@ -333,12 +337,12 @@ public class GameController implements GameCallback { logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId()); } ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + " is pending to join the game", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); - if (user.getSecondsDisconnected() > 240) { + if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) { + // TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger // Cancel player join possibility lately after 4 minutes logger.debug("Player " + player.getName() + " - canceled game (after 240 seconds) gameId: " + game.getId()); player.leave(); } - } } else if (!player.hasLeft()) { logger.debug("Player " + player.getName() + " canceled game (no user) gameId: " + game.getId()); diff --git a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java index 382021e4cf..ae2a758128 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -12,6 +12,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; +import mage.game.GameCommanderImpl; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.RandomUtil; @@ -31,7 +32,8 @@ import java.util.stream.Collectors; */ public final class SystemUtil { - private SystemUtil(){} + private SystemUtil() { + } public static final DateFormat dateFormat = new SimpleDateFormat("yy-M-dd HH:mm:ss"); @@ -485,6 +487,8 @@ public final class SystemUtil { gameZone = Zone.COMMAND; } else if ("plane".equalsIgnoreCase(command.zone)) { gameZone = Zone.COMMAND; + } else if ("commander".equalsIgnoreCase(command.zone)) { + gameZone = Zone.COMMAND; } else { logger.warn("Unknown zone [" + command.zone + "]: " + line); continue; @@ -513,8 +517,23 @@ public final class SystemUtil { } } game.loadCards(cardsToLoad, player.getId()); - for (Card card : cardsToLoad) { - swapWithAnyCard(game, player, card, gameZone); + + if ("commander".equalsIgnoreCase(command.zone) && cardsToLoad.size() > 0) { + // as commander (only commander games, look at init code in GameCommanderImpl) + if (game instanceof GameCommanderImpl) { + GameCommanderImpl gameCommander = (GameCommanderImpl) game; + for (Card card : cardsToLoad) { + player.addCommanderId(card.getId()); + gameCommander.initCommander(card, player); + } + } else { + logger.fatal("Commander card can be used in commander game only: " + command.cardName); + } + } else { + // as other card + for (Card card : cardsToLoad) { + swapWithAnyCard(game, player, card, gameZone); + } } } } catch (Exception e) { diff --git a/Mage.Sets/pom.xml b/Mage.Sets/pom.xml index ee7ec2a74c..0b31d3522d 100644 --- a/Mage.Sets/pom.xml +++ b/Mage.Sets/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-sets diff --git a/Mage.Sets/src/mage/cards/a/AchHansRun.java b/Mage.Sets/src/mage/cards/a/AchHansRun.java index 18853779ff..cbaeab9c00 100644 --- a/Mage.Sets/src/mage/cards/a/AchHansRun.java +++ b/Mage.Sets/src/mage/cards/a/AchHansRun.java @@ -81,7 +81,7 @@ class AchHansRunEffect extends OneShotEffect { FilterCard nameFilter = new FilterCard(); nameFilter.add(new NamePredicate(cardName)); TargetCardInLibrary target = new TargetCardInLibrary(1, 1, nameFilter); - if (!controller.searchLibrary(target, game)) { + if (!controller.searchLibrary(target, source, game)) { return false; } Card card = controller.getLibrary().remove(target.getFirstTarget(), game); diff --git a/Mage.Sets/src/mage/cards/a/Acquire.java b/Mage.Sets/src/mage/cards/a/Acquire.java index 3eb0827a33..53895e0758 100644 --- a/Mage.Sets/src/mage/cards/a/Acquire.java +++ b/Mage.Sets/src/mage/cards/a/Acquire.java @@ -63,7 +63,7 @@ class AcquireEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (opponent != null && controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - controller.searchLibrary(target, game, opponent.getId()); + controller.searchLibrary(target, source, game, opponent.getId()); Card targetCard = game.getCard(target.getFirstTarget()); if (targetCard != null) { controller.moveCards(targetCard, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/a/AjaniTheGreathearted.java b/Mage.Sets/src/mage/cards/a/AjaniTheGreathearted.java index a66a7fe935..fe392e8e50 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniTheGreathearted.java +++ b/Mage.Sets/src/mage/cards/a/AjaniTheGreathearted.java @@ -38,13 +38,13 @@ public final class AjaniTheGreathearted extends CardImpl { this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.AJANI); - this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(0)); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); // Creatures you control have vigilance. this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, - StaticFilters.FILTER_CONTROLLED_CREATURES + StaticFilters.FILTER_PERMANENT_CREATURES ))); // +1: You gain 3 life. diff --git a/Mage.Sets/src/mage/cards/a/AllHallowsEve.java b/Mage.Sets/src/mage/cards/a/AllHallowsEve.java index e033af793a..d61463522c 100644 --- a/Mage.Sets/src/mage/cards/a/AllHallowsEve.java +++ b/Mage.Sets/src/mage/cards/a/AllHallowsEve.java @@ -1,7 +1,5 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.dynamicvalue.common.StaticValue; @@ -9,7 +7,9 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.cards.*; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.TargetController; @@ -19,8 +19,9 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class AllHallowsEve extends CardImpl { @@ -72,7 +73,7 @@ class AllHallowsEveEffect extends OneShotEffect { if (allHallowsEve != null && controller != null && game.getExile().getCard(allHallowsEve.getId(), game) != null) { - allHallowsEve.getCounters(game).removeCounter(CounterType.SCREAM, 1); + allHallowsEve.removeCounters(CounterType.SCREAM.getName(), 1, game); if (allHallowsEve.getCounters(game).getCount(CounterType.SCREAM) == 0) { allHallowsEve.moveToZone(Zone.GRAVEYARD, source.getId(), game, false); for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { diff --git a/Mage.Sets/src/mage/cards/a/AngrathCaptainOfChaos.java b/Mage.Sets/src/mage/cards/a/AngrathCaptainOfChaos.java index 2cd5062e00..9827fa6684 100644 --- a/Mage.Sets/src/mage/cards/a/AngrathCaptainOfChaos.java +++ b/Mage.Sets/src/mage/cards/a/AngrathCaptainOfChaos.java @@ -30,12 +30,12 @@ public final class AngrathCaptainOfChaos extends CardImpl { // Creatures you control have menace. this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( - new MenaceAbility(), Duration.WhileOnBattlefield, + new MenaceAbility(false), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES ))); - // -2: Amass 2. - this.addAbility(new LoyaltyAbility(new AmassEffect(2))); + // -2: Amass 2. (Put two +1/+1 counters on an Army you control. If you don’t control one, create a 0/0 black Zombie Army creature token first.) + this.addAbility(new LoyaltyAbility(new AmassEffect(2), -2)); } private AngrathCaptainOfChaos(final AngrathCaptainOfChaos card) { diff --git a/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java b/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java index 7a1ef89b36..3c4d17cc49 100644 --- a/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java +++ b/Mage.Sets/src/mage/cards/a/ArachnusSpinner.java @@ -106,7 +106,7 @@ class ArachnusSpinnerEffect extends OneShotEffect { } if (card == null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { card = game.getCard(target.getFirstTarget()); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/a/ArborealGrazer.java b/Mage.Sets/src/mage/cards/a/ArborealGrazer.java new file mode 100644 index 0000000000..445bf3daac --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArborealGrazer.java @@ -0,0 +1,45 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArborealGrazer extends CardImpl { + + public ArborealGrazer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When Arboreal Grazer enters the battlefield, you may put a land card from your hand onto the battlefield tapped. + this.addAbility(new EntersBattlefieldTriggeredAbility(new PutCardFromHandOntoBattlefieldEffect( + StaticFilters.FILTER_CARD_LAND_A, false, true + ), false)); + + } + + private ArborealGrazer(final ArborealGrazer card) { + super(card); + } + + @Override + public ArborealGrazer copy() { + return new ArborealGrazer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArchmageAscension.java b/Mage.Sets/src/mage/cards/a/ArchmageAscension.java index efbcafce29..e71f21b2b6 100644 --- a/Mage.Sets/src/mage/cards/a/ArchmageAscension.java +++ b/Mage.Sets/src/mage/cards/a/ArchmageAscension.java @@ -111,7 +111,7 @@ class ArchmageAscensionReplacementEffect extends ReplacementEffectImpl { Player player = game.getPlayer(event.getPlayerId()); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { card.moveToZone(Zone.HAND, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/a/ArcumDagsson.java b/Mage.Sets/src/mage/cards/a/ArcumDagsson.java index d07d474fe3..c86c8389d4 100644 --- a/Mage.Sets/src/mage/cards/a/ArcumDagsson.java +++ b/Mage.Sets/src/mage/cards/a/ArcumDagsson.java @@ -90,7 +90,7 @@ class ArcumDagssonEffect extends OneShotEffect { artifactCreature.sacrifice(source.getSourceId(), game); if (player.chooseUse(Outcome.PutCardInPlay, "Search your library for a noncreature artifact card?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { player.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/a/ArlinnVoiceOfThePack.java b/Mage.Sets/src/mage/cards/a/ArlinnVoiceOfThePack.java index df2157c094..705531fe94 100644 --- a/Mage.Sets/src/mage/cards/a/ArlinnVoiceOfThePack.java +++ b/Mage.Sets/src/mage/cards/a/ArlinnVoiceOfThePack.java @@ -34,7 +34,7 @@ public final class ArlinnVoiceOfThePack extends CardImpl { this.addAbility(new SimpleStaticAbility(new ArlinnVoiceOfThePackReplacementEffect())); // -2: Create a 2/2 green Wolf creature token. - this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new WolfToken()), -2)); + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new WolfToken("WAR")), -2)); } private ArlinnVoiceOfThePack(final ArlinnVoiceOfThePack card) { diff --git a/Mage.Sets/src/mage/cards/a/ArmoryAutomaton.java b/Mage.Sets/src/mage/cards/a/ArmoryAutomaton.java index f4fe5b25bb..f56d6acc10 100644 --- a/Mage.Sets/src/mage/cards/a/ArmoryAutomaton.java +++ b/Mage.Sets/src/mage/cards/a/ArmoryAutomaton.java @@ -88,7 +88,9 @@ class ArmoryAutomatonEffect extends OneShotEffect { while (player.canRespond() && countBattlefield > 0 && player.chooseUse(Outcome.Benefit, "Select and attach a target Equipment?", source, game)) { Target targetEquipment = new TargetPermanent(currentFilter); targetEquipment.setRequired(false); - if (player.choose(Outcome.Benefit, targetEquipment, source.getSourceId(), game)) { + if (player.choose(Outcome.Benefit, targetEquipment, source.getSourceId(), game) && targetEquipment.getFirstTarget() != null) { + currentFilter.add(Predicates.not(new PermanentIdPredicate(targetEquipment.getFirstTarget()))); // exclude selected for next time + Permanent aura = game.getPermanent(targetEquipment.getFirstTarget()); if (aura != null) { Permanent attachedTo = game.getPermanent(aura.getAttachedTo()); @@ -96,10 +98,9 @@ class ArmoryAutomatonEffect extends OneShotEffect { attachedTo.removeAttachment(aura.getId(), game); } sourcePermanent.addAttachment(aura.getId(), game); - - // exclude selected - currentFilter.add(Predicates.not(new PermanentIdPredicate(aura.getId()))); } + } else { + break; } countBattlefield = game.getBattlefield().getAllActivePermanents(currentFilter, game).size(); } diff --git a/Mage.Sets/src/mage/cards/a/AshiokDreamRender.java b/Mage.Sets/src/mage/cards/a/AshiokDreamRender.java new file mode 100644 index 0000000000..ac680020c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AshiokDreamRender.java @@ -0,0 +1,97 @@ +package mage.cards.a; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.ExileGraveyardAllPlayersEffect; +import mage.abilities.effects.common.PutLibraryIntoGraveTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AshiokDreamRender extends CardImpl { + + public AshiokDreamRender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U/B}{U/B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ASHIOK); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Spells and abilities your opponents control can't cause their controller to search their library. + this.addAbility(new SimpleStaticAbility(new AshiokDreamRenderEffect())); + + // -1: Target player puts the top four cards of their library into their graveyard. Then exile each opponent's graveyard. + Ability ability = new LoyaltyAbility(new PutLibraryIntoGraveTargetEffect(4), -1); + ability.addEffect(new ExileGraveyardAllPlayersEffect(StaticFilters.FILTER_CARD, TargetController.OPPONENT).setText("Then exile each opponent's graveyard.")); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + private AshiokDreamRender(final AshiokDreamRender card) { + super(card); + } + + @Override + public AshiokDreamRender copy() { + return new AshiokDreamRender(this); + } +} + +class AshiokDreamRenderEffect extends ContinuousRuleModifyingEffectImpl { + + AshiokDreamRenderEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, true, false); + staticText = "Spells and abilities your opponents control can't cause their controller to search their library."; + } + + private AshiokDreamRenderEffect(final AshiokDreamRenderEffect effect) { + super(effect); + } + + @Override + public AshiokDreamRenderEffect copy() { + return new AshiokDreamRenderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + MageObject mageObject = game.getObject(source.getSourceId()); + if (mageObject != null) { + return "You can't search libraries (" + mageObject.getLogName() + " in play)."; + } + return null; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return GameEvent.EventType.SEARCH_LIBRARY == event.getType(); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + return controller != null + && event.getPlayerId().equals(game.getControllerId(event.getSourceId())) + && event.getTargetId().equals(game.getControllerId(event.getSourceId())) + && controller.hasOpponent(game.getControllerId(event.getSourceId()), game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AshioksSkulker.java b/Mage.Sets/src/mage/cards/a/AshioksSkulker.java new file mode 100644 index 0000000000..e67a4f5e3e --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AshioksSkulker.java @@ -0,0 +1,41 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AshioksSkulker extends CardImpl { + + public AshioksSkulker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // {3}{U}: Ashiok's Skulker can't be blocked this turn. + this.addAbility(new SimpleActivatedAbility( + new CantBeBlockedSourceEffect(Duration.EndOfTurn), new ManaCostsImpl("{3}{U}") + )); + } + + private AshioksSkulker(final AshioksSkulker card) { + super(card); + } + + @Override + public AshioksSkulker copy() { + return new AshioksSkulker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AssassinsTrophy.java b/Mage.Sets/src/mage/cards/a/AssassinsTrophy.java index 45ee06cb1a..a973855cc4 100644 --- a/Mage.Sets/src/mage/cards/a/AssassinsTrophy.java +++ b/Mage.Sets/src/mage/cards/a/AssassinsTrophy.java @@ -77,7 +77,7 @@ class AssassinsTrophyEffect extends OneShotEffect { if (controller != null) { if (controller.chooseUse(Outcome.PutLandInPlay, "Do you wish to search for a basic land, put it onto the battlefield and then shuffle your library?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/a/AuratouchedMage.java b/Mage.Sets/src/mage/cards/a/AuratouchedMage.java index 5bd4e110db..7cb38b3de8 100644 --- a/Mage.Sets/src/mage/cards/a/AuratouchedMage.java +++ b/Mage.Sets/src/mage/cards/a/AuratouchedMage.java @@ -69,7 +69,7 @@ class AuratouchedMageEffect extends OneShotEffect { filter.add(new AuraCardCanAttachToLKIPermanentId(source.getSourceId())); TargetCardInLibrary target = new TargetCardInLibrary(filter); target.setNotTarget(true); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (target.getFirstTarget() != null) { Card aura = game.getCard(target.getFirstTarget()); Permanent auratouchedMage = source.getSourcePermanentIfItStillExists(game); diff --git a/Mage.Sets/src/mage/cards/a/Aurification.java b/Mage.Sets/src/mage/cards/a/Aurification.java index ef96fbf8ce..3d85d0f83e 100644 --- a/Mage.Sets/src/mage/cards/a/Aurification.java +++ b/Mage.Sets/src/mage/cards/a/Aurification.java @@ -1,7 +1,5 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; @@ -24,8 +22,9 @@ import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import mage.util.SubTypeList; +import java.util.UUID; + /** - * * @author andyfries */ @@ -40,7 +39,7 @@ public final class Aurification extends CardImpl { static final String rule = "Each creature with a gold counter on it is a Wall in addition to its other creature types and has defender."; public Aurification(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); // Whenever a creature deals damage to you, put a gold counter on it. this.addAbility(new AddGoldCountersAbility()); @@ -127,8 +126,11 @@ public final class Aurification extends CardImpl { @Override public boolean apply(Game game, Ability source) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(CardType.CREATURE)) { - if (permanent != null){ - permanent.getCounters(game).removeAllCounters(CounterType.GOLD); + if (permanent != null) { + int numToRemove = permanent.getCounters(game).getCount(CounterType.GOLD); + if (numToRemove > 0) { + permanent.removeCounters(CounterType.GOLD.getName(), numToRemove, game); + } } } return true; diff --git a/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java b/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java index 41cb467003..cd41b415dd 100644 --- a/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java +++ b/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java @@ -78,7 +78,7 @@ class AvatarOfGrowthSearchEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/a/AvenEternal.java b/Mage.Sets/src/mage/cards/a/AvenEternal.java new file mode 100644 index 0000000000..fa0afe4db3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvenEternal.java @@ -0,0 +1,43 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.AmassEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvenEternal extends CardImpl { + + public AvenEternal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Aven Eternal enters the battlefield, amass 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AmassEffect(1))); + } + + private AvenEternal(final AvenEternal card) { + super(card); + } + + @Override + public AvenEternal copy() { + return new AvenEternal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AwakeningOfVituGhazi.java b/Mage.Sets/src/mage/cards/a/AwakeningOfVituGhazi.java new file mode 100644 index 0000000000..b7b2b87c67 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AwakeningOfVituGhazi.java @@ -0,0 +1,66 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.TokenImpl; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AwakeningOfVituGhazi extends CardImpl { + + public AwakeningOfVituGhazi(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}{G}"); + + // Put nine +1/+1 counters on target land you control. It becomes a legendary 0/0 Elemental creature with haste named Vitu-Ghazi. It's still a land. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(9))); + this.getSpellAbility().addEffect(new BecomesCreatureTargetEffect( + new AwakeningOfVituGhaziToken(), false, true, Duration.Custom, true + ).setText("It becomes a legendary 0/0 Elemental creature with haste named Vitu-Ghazi. It's still a land.")); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND)); + } + + private AwakeningOfVituGhazi(final AwakeningOfVituGhazi card) { + super(card); + } + + @Override + public AwakeningOfVituGhazi copy() { + return new AwakeningOfVituGhazi(this); + } +} + +class AwakeningOfVituGhaziToken extends TokenImpl { + + AwakeningOfVituGhaziToken() { + super("Vitu-Ghazi", "legendary 0/0 Elemental creature with haste named Vitu-Ghazi"); + this.supertype.add(SuperType.LEGENDARY); + this.cardType.add(CardType.CREATURE); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + this.addAbility(HasteAbility.getInstance()); + } + + private AwakeningOfVituGhaziToken(final AwakeningOfVituGhaziToken token) { + super(token); + } + + public AwakeningOfVituGhaziToken copy() { + return new AwakeningOfVituGhaziToken(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BandTogether.java b/Mage.Sets/src/mage/cards/b/BandTogether.java new file mode 100644 index 0000000000..ec4b774035 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BandTogether.java @@ -0,0 +1,99 @@ +package mage.cards.b; + +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.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BandTogether extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creatures you control"); + private static final FilterPermanent filter2 = new FilterCreaturePermanent("another target creature"); + + static { + filter.add(new AnotherTargetPredicate(1)); + filter2.add(new AnotherTargetPredicate(2)); + } + + public BandTogether(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); + + // Up to two target creatures you control each deal damage equal to their power to another target creature. + this.getSpellAbility().addEffect(new BandTogetherEffect()); + Target target = new TargetPermanent(0, 2, filter, false); + target.setTargetTag(1); + this.getSpellAbility().addTarget(target); + target = new TargetPermanent(1, 1, filter2, false); + target.setTargetTag(2); + this.getSpellAbility().addTarget(target); + } + + private BandTogether(final BandTogether card) { + super(card); + } + + @Override + public BandTogether copy() { + return new BandTogether(this); + } +} + +class BandTogetherEffect extends OneShotEffect { + + BandTogetherEffect() { + super(Outcome.Benefit); + this.staticText = "Up to two target creatures you control each deal damage equal to their power to another target creature."; + } + + private BandTogetherEffect(final BandTogetherEffect effect) { + super(effect); + } + + @Override + public BandTogetherEffect copy() { + return new BandTogetherEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getTargets().size() < 2) { + return false; + } + + Target damageTarget = source.getTargets().get(0); + Target destTarget = source.getTargets().get(1); + if (damageTarget.getTargets().isEmpty() || destTarget.getTargets().isEmpty()) { + return false; + } + + Permanent permanentDamage1 = damageTarget.getTargets().size() < 1 ? null : game.getPermanent(damageTarget.getTargets().get(0)); + Permanent permanentDamage2 = damageTarget.getTargets().size() < 2 ? null : game.getPermanent(damageTarget.getTargets().get(1)); + Permanent permanentDest = game.getPermanent(destTarget.getTargets().get(0)); + if (permanentDest == null) { + return false; + } + + if (permanentDamage1 != null) { + permanentDest.damage(permanentDamage1.getPower().getValue(), permanentDamage1.getId(), game, false, true); + } + if (permanentDamage2 != null) { + permanentDest.damage(permanentDamage2.getPower().getValue(), permanentDamage2.getId(), game, false, true); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BattlefieldPromotion.java b/Mage.Sets/src/mage/cards/b/BattlefieldPromotion.java new file mode 100644 index 0000000000..c86972f4c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BattlefieldPromotion.java @@ -0,0 +1,41 @@ +package mage.cards.b; + +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BattlefieldPromotion extends CardImpl { + + public BattlefieldPromotion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Put a +1/+1 counter on target creature. That creature gains first strike until end of turn. You gain 2 life. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn + ).setText("That creature gains first strike until end of turn.")); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private BattlefieldPromotion(final BattlefieldPromotion card) { + super(card); + } + + @Override + public BattlefieldPromotion copy() { + return new BattlefieldPromotion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BioessenceHydra.java b/Mage.Sets/src/mage/cards/b/BioessenceHydra.java new file mode 100644 index 0000000000..2d591b829f --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BioessenceHydra.java @@ -0,0 +1,130 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +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.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BioessenceHydra extends CardImpl { + + public BioessenceHydra(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{U}"); + + this.subtype.add(SubType.HYDRA); + this.subtype.add(SubType.MUTANT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Bioessence Hydra enters the battlefield with a +1/+1 counter on it for each loyalty counter on planeswalkers you control. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), BioessenceHydraDynamicValue.instance, true + ), "with a +1/+1 counter on it for each loyalty counter on planeswalkers you control." + )); + + // Whenever one or more loyalty counters are put on planeswalkers you control, put that many +1/+1 counters on Bioessence Hydra. + this.addAbility(new BioessenceHydraTriggeredAbility()); + } + + private BioessenceHydra(final BioessenceHydra card) { + super(card); + } + + @Override + public BioessenceHydra copy() { + return new BioessenceHydra(this); + } +} + +enum BioessenceHydraDynamicValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int counter = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents( + StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceAbility.getControllerId(), game + )) { + if (permanent != null) { + counter += permanent.getCounters(game).getCount(CounterType.LOYALTY); + } + } + return counter; + } + + @Override + public DynamicValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} + +class BioessenceHydraTriggeredAbility extends TriggeredAbilityImpl { + + BioessenceHydraTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false); + } + + private BioessenceHydraTriggeredAbility(final BioessenceHydraTriggeredAbility ability) { + super(ability); + } + + @Override + public BioessenceHydraTriggeredAbility copy() { + return new BioessenceHydraTriggeredAbility(this); + } + + @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.LOYALTY.getName()) && event.getAmount() > 0) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + } + if (permanent != null + && !event.getTargetId().equals(this.getSourceId()) + && permanent.isPlaneswalker() + && permanent.isControlledBy(this.getControllerId())) { + this.getEffects().clear(); + this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(event.getAmount()))); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever one or more loyalty counters are put on a planeswalker you control, put that many +1/+1 counters on {this}."; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BirthingPod.java b/Mage.Sets/src/mage/cards/b/BirthingPod.java index 179040611b..76a8df1d7d 100644 --- a/Mage.Sets/src/mage/cards/b/BirthingPod.java +++ b/Mage.Sets/src/mage/cards/b/BirthingPod.java @@ -91,7 +91,7 @@ class BirthingPodEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, newConvertedCost)); filter.add(new CardTypePredicate(CardType.CREATURE)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/b/BitterOrdeal.java b/Mage.Sets/src/mage/cards/b/BitterOrdeal.java index 1611148bff..7a0fd7d729 100644 --- a/Mage.Sets/src/mage/cards/b/BitterOrdeal.java +++ b/Mage.Sets/src/mage/cards/b/BitterOrdeal.java @@ -66,7 +66,7 @@ class BitterOrdealEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && targetPlayer != null) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (controller.searchLibrary(target, game, targetPlayer.getId())) { + if (controller.searchLibrary(target, source, game, targetPlayer.getId())) { Card card = targetPlayer.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCardToExileWithInfo(card, null, null, source.getSourceId(), game, Zone.LIBRARY, true); diff --git a/Mage.Sets/src/mage/cards/b/BitterheartWitch.java b/Mage.Sets/src/mage/cards/b/BitterheartWitch.java index b3158adf6e..c010a92f29 100644 --- a/Mage.Sets/src/mage/cards/b/BitterheartWitch.java +++ b/Mage.Sets/src/mage/cards/b/BitterheartWitch.java @@ -76,7 +76,7 @@ class BitterheartWitchEffect extends OneShotEffect { Player targetPlayer = game.getPlayer(source.getFirstTarget()); if (controller != null && targetPlayer != null) { TargetCardInLibrary targetCard = new TargetCardInLibrary(filter); - if (controller.searchLibrary(targetCard, game)) { + if (controller.searchLibrary(targetCard, source, game)) { Card card = game.getCard(targetCard.getFirstTarget()); if (card != null) { game.getState().setValue("attachTo:" + card.getId(), targetPlayer.getId()); diff --git a/Mage.Sets/src/mage/cards/b/BlastZone.java b/Mage.Sets/src/mage/cards/b/BlastZone.java new file mode 100644 index 0000000000..982b139f1c --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BlastZone.java @@ -0,0 +1,95 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +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.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BlastZone extends CardImpl { + + public BlastZone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + + // Blast Zone enters the battlefield with a charge counter on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance(1)) + )); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {X}{X}, {T}: Put X charge counters on Blast Zone. + Ability ability = new SimpleActivatedAbility(new AddCountersSourceEffect( + CounterType.CHARGE.createInstance(), ManacostVariableValue.instance, true + ), new ManaCostsImpl("{X}{X}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // {3}, {T}, Sacrifice Blast Zone: Destroy each nonland permanent with converted mana cost equal to the number of charge counters on Blast Zone. + ability = new SimpleActivatedAbility(new BlastZoneEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private BlastZone(final BlastZone card) { + super(card); + } + + @Override + public BlastZone copy() { + return new BlastZone(this); + } +} + +class BlastZoneEffect extends OneShotEffect { + + BlastZoneEffect() { + super(Outcome.Benefit); + staticText = "Destroy each nonland permanent with converted mana cost " + + "equal to the number of charge counters on {this}"; + } + + private BlastZoneEffect(final BlastZoneEffect effect) { + super(effect); + } + + @Override + public BlastZoneEffect copy() { + return new BlastZoneEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + int xValue = permanent.getCounters(game).getCount(CounterType.CHARGE); + FilterPermanent filter = new FilterNonlandPermanent(); + filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, xValue)); + return new DestroyAllEffect(filter).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BleedingEdge.java b/Mage.Sets/src/mage/cards/b/BleedingEdge.java new file mode 100644 index 0000000000..d3c4ffae6b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BleedingEdge.java @@ -0,0 +1,36 @@ +package mage.cards.b; + +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.keyword.AmassEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BleedingEdge extends CardImpl { + + public BleedingEdge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); + + // Up to one target creature gets -2/-2 until end of turn. Amass 2. + this.getSpellAbility().addEffect(new BoostTargetEffect(-2, -2, Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.getSpellAbility().addEffect(new AmassEffect(2)); + } + + private BleedingEdge(final BleedingEdge card) { + super(card); + } + + @Override + public BleedingEdge copy() { + return new BleedingEdge(this); + } +} +// It's nanotech, you like it? \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/Blindblast.java b/Mage.Sets/src/mage/cards/b/Blindblast.java index 4af8707029..4d6abe5a47 100644 --- a/Mage.Sets/src/mage/cards/b/Blindblast.java +++ b/Mage.Sets/src/mage/cards/b/Blindblast.java @@ -1,5 +1,6 @@ package mage.cards.b; +import java.util.UUID; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; @@ -9,8 +10,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetCreaturePermanent; -import java.util.UUID; - /** * @author TheElk801 */ @@ -23,7 +22,7 @@ public final class Blindblast extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(1)); this.getSpellAbility().addEffect(new CantBlockTargetEffect( Duration.EndOfTurn - ).setText("That creature can't block this turn.")); + ).setText("That creature can't block this turn")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Draw a card. diff --git a/Mage.Sets/src/mage/cards/b/BloomHulk.java b/Mage.Sets/src/mage/cards/b/BloomHulk.java index 6c991a9536..c5631c3dc1 100644 --- a/Mage.Sets/src/mage/cards/b/BloomHulk.java +++ b/Mage.Sets/src/mage/cards/b/BloomHulk.java @@ -23,7 +23,7 @@ public final class BloomHulk extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(4); - // When Bloom Hulk enters the battlefield, proliferate. + // When Bloom Hulk enters the battlefield, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) this.addAbility(new EntersBattlefieldTriggeredAbility(new ProliferateEffect())); } diff --git a/Mage.Sets/src/mage/cards/b/BolassCitadel.java b/Mage.Sets/src/mage/cards/b/BolassCitadel.java new file mode 100644 index 0000000000..b66b461f2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BolassCitadel.java @@ -0,0 +1,118 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; +import mage.abilities.costs.Cost; + +/** + * @author jeffwadsworth + */ +public final class BolassCitadel extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("nonland permanents"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public BolassCitadel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{B}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost. + this.addAbility(new SimpleStaticAbility(new BolassCitadelPlayTheTopCardEffect())); + + // {T}, Sacrifice ten nonland permanents: Each opponent loses 10 life. + Ability ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(10), new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent( + 10, 10, filter, true + ))); + this.addAbility(ability); + } + + private BolassCitadel(final BolassCitadel card) { + super(card); + } + + @Override + public BolassCitadel copy() { + return new BolassCitadel(this); + } +} + +class BolassCitadelPlayTheTopCardEffect extends AsThoughEffectImpl { + + BolassCitadelPlayTheTopCardEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, + Duration.WhileOnBattlefield, Outcome.AIDontUseIt); // AI will need help with this + staticText = "You may play the top card of your library. If you cast a spell this way, " + + "pay life equal to its converted mana cost rather than pay its mana cost."; + } + + private BolassCitadelPlayTheTopCardEffect(final BolassCitadelPlayTheTopCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public BolassCitadelPlayTheTopCardEffect copy() { + return new BolassCitadelPlayTheTopCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card cardOnTop = game.getCard(objectId); + if (cardOnTop == null) { + return false; + } + if (affectedControllerId.equals(source.getControllerId()) + && cardOnTop.isOwnedBy(source.getControllerId())) { + Player controller = game.getPlayer(cardOnTop.getOwnerId()); + if (controller != null + && cardOnTop.equals(controller.getLibrary().getFromTop(game))) { + // add the life cost first + PayLifeCost cost = new PayLifeCost(cardOnTop.getManaCost().convertedManaCost()); + Costs costs = new CostsImpl(); + costs.add(cost); + // check for additional costs that must be paid + if (cardOnTop.getSpellAbility() != null) { + for (Cost additionalCost : cardOnTop.getSpellAbility().getCosts()) { + costs.add(additionalCost); + } + } + controller.setCastSourceIdWithAlternateMana(cardOnTop.getId(), null, costs); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoldwyrHeavyweights.java b/Mage.Sets/src/mage/cards/b/BoldwyrHeavyweights.java index 02789e13b3..f86567ffc2 100644 --- a/Mage.Sets/src/mage/cards/b/BoldwyrHeavyweights.java +++ b/Mage.Sets/src/mage/cards/b/BoldwyrHeavyweights.java @@ -73,7 +73,7 @@ class BoldwyrHeavyweightsEffect extends OneShotEffect { Player opponent = game.getPlayer(opponentId); if (opponent != null && opponent.chooseUse(Outcome.PutCreatureInPlay, "Search your library for a creature card and put it onto the battlefield?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(new FilterCreatureCard()); - if (opponent.searchLibrary(target, game)) { + if (opponent.searchLibrary(target, source, game)) { Card targetCard = opponent.getLibrary().getCard(target.getFirstTarget(), game); if (targetCard != null) { opponent.moveCards(targetCard, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/b/BoltBend.java b/Mage.Sets/src/mage/cards/b/BoltBend.java new file mode 100644 index 0000000000..a60ced09ba --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoltBend.java @@ -0,0 +1,49 @@ +package mage.cards.b; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.predicate.mageobject.NumberOfTargetsPredicate; + +import java.util.UUID; +import mage.filter.FilterStackObject; +import mage.target.TargetStackObject; + +/** + * @author TheElk801 + */ +public final class BoltBend extends CardImpl { + + private static final FilterStackObject filter = new FilterStackObject("spell or ability with a single target"); + + static { + filter.add(new NumberOfTargetsPredicate(1)); + } + + public BoltBend(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R}"); + + // This spell costs {3} less to cast if you control a creature with power 4 or greater. + this.addAbility(new SimpleStaticAbility( + Zone.STACK, new SpellCostReductionSourceEffect(3, FerociousCondition.instance) + ).setRuleAtTheTop(true)); + + // Change the target of target spell or ability with a single target. + this.getSpellAbility().addEffect(new ChooseNewTargetsTargetEffect(true, true)); + this.getSpellAbility().addTarget(new TargetStackObject(filter)); + } + + private BoltBend(final BoltBend card) { + super(card); + } + + @Override + public BoltBend copy() { + return new BoltBend(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BondOfDiscipline.java b/Mage.Sets/src/mage/cards/b/BondOfDiscipline.java new file mode 100644 index 0000000000..d5980c6ca7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BondOfDiscipline.java @@ -0,0 +1,43 @@ +package mage.cards.b; + +import mage.abilities.effects.common.TapAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BondOfDiscipline extends CardImpl { + + private static final FilterPermanent filter + = new FilterOpponentsCreaturePermanent("creatures your opponents control."); + + public BondOfDiscipline(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}"); + + // Tap all creatures your opponents control. Creatures you control gain lifelink until end of turn. + this.getSpellAbility().addEffect(new TapAllEffect(filter)); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + LifelinkAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + )); + } + + private BondOfDiscipline(final BondOfDiscipline card) { + super(card); + } + + @Override + public BondOfDiscipline copy() { + return new BondOfDiscipline(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BondOfFlourishing.java b/Mage.Sets/src/mage/cards/b/BondOfFlourishing.java new file mode 100644 index 0000000000..bba9076cfe --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BondOfFlourishing.java @@ -0,0 +1,40 @@ +package mage.cards.b; + +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BondOfFlourishing extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard(); + + public BondOfFlourishing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Look at the top three card of your library. You may reveal a permanent card from among them and put it into your hand. Put the rest on the bottom of your library in any order. You gain 3 life. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + new StaticValue(3), false, + new StaticValue(1), filter, false + )); + this.getSpellAbility().addEffect(new GainLifeEffect(3).setText("You gain 3 life.")); + } + + private BondOfFlourishing(final BondOfFlourishing card) { + super(card); + } + + @Override + public BondOfFlourishing copy() { + return new BondOfFlourishing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BondOfInsight.java b/Mage.Sets/src/mage/cards/b/BondOfInsight.java new file mode 100644 index 0000000000..fcd7cdda7e --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BondOfInsight.java @@ -0,0 +1,83 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +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.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 BondOfInsight extends CardImpl { + + public BondOfInsight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); + + // Each player puts the top four cards of their library into their graveyard. Return up to two instant and/or sorcery cards from your graveyard to your hand. Exile Bond of Insight. + this.getSpellAbility().addEffect(new BondOfInsightEffect()); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + private BondOfInsight(final BondOfInsight card) { + super(card); + } + + @Override + public BondOfInsight copy() { + return new BondOfInsight(this); + } +} + +class BondOfInsightEffect extends OneShotEffect { + + BondOfInsightEffect() { + super(Outcome.Benefit); + staticText = "Each player puts the top four cards of their library into their graveyard. " + + "Return up to two instant and/or sorcery cards from your graveyard to your hand."; + } + + private BondOfInsightEffect(final BondOfInsightEffect effect) { + super(effect); + } + + @Override + public BondOfInsightEffect copy() { + return new BondOfInsightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + player.moveCards(player.getLibrary().getTopCards(game, 4), Zone.GRAVEYARD, source, game); + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new TargetCardInYourGraveyard( + 0, 2, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, true + ); + if (!player.choose(outcome, target, source.getSourceId(), game)) { + return false; + } + Cards cards = new CardsImpl(target.getTargets()); + return player.moveCards(cards, Zone.HAND, source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BondOfPassion.java b/Mage.Sets/src/mage/cards/b/BondOfPassion.java new file mode 100644 index 0000000000..a68664370b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BondOfPassion.java @@ -0,0 +1,108 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetAnyTarget; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BondOfPassion extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent(); + private static final FilterCreaturePlayerOrPlaneswalker filter2 + = new FilterCreaturePlayerOrPlaneswalker("any other target"); + + static { + filter.add(new AnotherTargetPredicate(1)); + filter2.getCreatureFilter().add(new AnotherTargetPredicate(2)); + filter2.getPlaneswalkerFilter().add(new AnotherTargetPredicate(2)); + filter2.getPlayerFilter().add(new AnotherTargetPredicate(2)); + } + + public BondOfPassion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + + // Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. Bond of Passion deals 2 damage to any other target. + this.getSpellAbility().addEffect(new BondOfPassionEffect()); + Target target = new TargetPermanent(filter); + target.setTargetTag(1); + this.getSpellAbility().addTarget(target); + target = new TargetAnyTarget(filter2); + target.setTargetTag(2); + this.getSpellAbility().addTarget(target); + } + + private BondOfPassion(final BondOfPassion card) { + super(card); + } + + @Override + public BondOfPassion copy() { + return new BondOfPassion(this); + } +} + +class BondOfPassionEffect extends OneShotEffect { + + BondOfPassionEffect() { + super(Outcome.Benefit); + staticText = "Gain control of target creature until end of turn. Untap that creature. " + + "It gains haste until end of turn. {this} deals 2 damage to any other target."; + } + + private BondOfPassionEffect(final BondOfPassionEffect effect) { + super(effect); + } + + @Override + public BondOfPassionEffect copy() { + return new BondOfPassionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + ContinuousEffect effect = new GainControlTargetEffect(Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + permanent.untap(game); + effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + } + Permanent permanent2 = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (permanent2 != null) { + permanent2.damage(2, source.getSourceId(), game); + return true; + } + Player player = game.getPlayer(source.getTargets().get(1).getFirstTarget()); + if (player != null) { + player.damage(2, source.getSourceId(), game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BondOfRevival.java b/Mage.Sets/src/mage/cards/b/BondOfRevival.java new file mode 100644 index 0000000000..e10a10f214 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BondOfRevival.java @@ -0,0 +1,81 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +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; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BondOfRevival extends CardImpl { + + public BondOfRevival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); + + // Return target creature card from your graveyard to the battlefield. It gains haste until your next turn. + this.getSpellAbility().addEffect(new BondOfRevivalEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard( + StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD + )); + } + + private BondOfRevival(final BondOfRevival card) { + super(card); + } + + @Override + public BondOfRevival copy() { + return new BondOfRevival(this); + } +} + +class BondOfRevivalEffect extends OneShotEffect { + + BondOfRevivalEffect() { + super(Outcome.Benefit); + staticText = "Return target creature card from your graveyard to the battlefield. " + + "It gains haste until your next turn."; + } + + private BondOfRevivalEffect(final BondOfRevivalEffect effect) { + super(effect); + } + + @Override + public BondOfRevivalEffect copy() { + return new BondOfRevivalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(source.getFirstTarget()); + if (player == null || card == null) { + return false; + } + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.UntilYourNextTurn); + effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1)); + if (player.moveCards(card, Zone.BATTLEFIELD, source, game)) { + game.addEffect(effect, source); + } + return true; + } +} + + diff --git a/Mage.Sets/src/mage/cards/b/BoonweaverGiant.java b/Mage.Sets/src/mage/cards/b/BoonweaverGiant.java index 4a7619452c..944c751ba4 100644 --- a/Mage.Sets/src/mage/cards/b/BoonweaverGiant.java +++ b/Mage.Sets/src/mage/cards/b/BoonweaverGiant.java @@ -101,7 +101,7 @@ class BoonweaverGiantEffect extends OneShotEffect { } if (card == null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { card = game.getCard(target.getFirstTarget()); if (card != null) { zone = Zone.LIBRARY; diff --git a/Mage.Sets/src/mage/cards/b/BorderlandExplorer.java b/Mage.Sets/src/mage/cards/b/BorderlandExplorer.java index f0e1fdf00f..d4a61d6b65 100644 --- a/Mage.Sets/src/mage/cards/b/BorderlandExplorer.java +++ b/Mage.Sets/src/mage/cards/b/BorderlandExplorer.java @@ -108,7 +108,7 @@ class BorderlandExplorerEffect extends OneShotEffect { Cards cardsPlayer = cardsToDiscard.get(playerId); if (cardsPlayer != null && !cardsPlayer.isEmpty()) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(target.getTargets()); cards.addAll(target.getTargets()); diff --git a/Mage.Sets/src/mage/cards/b/BoreasCharger.java b/Mage.Sets/src/mage/cards/b/BoreasCharger.java index 151cdfab2f..4050b02399 100644 --- a/Mage.Sets/src/mage/cards/b/BoreasCharger.java +++ b/Mage.Sets/src/mage/cards/b/BoreasCharger.java @@ -114,7 +114,7 @@ class BoreasChargerEffect extends OneShotEffect { TargetCardInLibrary target2 = new TargetCardInLibrary(0, landDifference, filter2); Cards cardsToHand = new CardsImpl(); - if (controller.searchLibrary(target2, game)) { + if (controller.searchLibrary(target2, source, game)) { for (UUID cardId : target2.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/b/BosiumStrip.java b/Mage.Sets/src/mage/cards/b/BosiumStrip.java new file mode 100644 index 0000000000..1a008842c7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BosiumStrip.java @@ -0,0 +1,142 @@ + +package mage.cards.b; + +import java.util.UUID; +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.AsThoughEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.watchers.common.CastFromGraveyardWatcher; + +/** + * + * @author spjspj & L_J + */ +public final class BosiumStrip extends CardImpl { + + public BosiumStrip(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // {3}, {T}: Until end of turn, if the top card of your graveyard is an instant or sorcery card, you may cast that card. If a card cast this way would be put into a graveyard this turn, exile it instead. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BosiumStripCastFromGraveyardEffect(), new ManaCostsImpl("{3}")); + ability.addCost(new TapSourceCost()); + ability.addEffect(new BosiumStripReplacementEffect()); + this.addAbility(ability, new CastFromGraveyardWatcher()); + } + + public BosiumStrip(final BosiumStrip card) { + super(card); + } + + @Override + public BosiumStrip copy() { + return new BosiumStrip(this); + } +} + +class BosiumStripCastFromGraveyardEffect extends AsThoughEffectImpl { + + BosiumStripCastFromGraveyardEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "Until end of turn, if the top card of your graveyard is an instant or sorcery card, you may cast that card"; + } + + BosiumStripCastFromGraveyardEffect(final BosiumStripCastFromGraveyardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public BosiumStripCastFromGraveyardEffect copy() { + return new BosiumStripCastFromGraveyardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!(source instanceof FlashbackAbility) + && affectedControllerId.equals(source.getControllerId())) { + Player player = game.getPlayer(affectedControllerId); + Card card = game.getCard(objectId); + if (card != null + && player != null + && card.equals(player.getGraveyard().getTopCard(game)) + && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game) + && game.getState().getZone(objectId) == Zone.GRAVEYARD) { + game.getState().setValue("BosiumStrip", card); + return true; + } + } + return false; + } +} + +class BosiumStripReplacementEffect extends ReplacementEffectImpl { + + BosiumStripReplacementEffect() { + super(Duration.EndOfTurn, Outcome.Exile); + staticText = "If a card cast this way would be put into a graveyard this turn, exile it instead"; + } + + BosiumStripReplacementEffect(final BosiumStripReplacementEffect effect) { + super(effect); + } + + @Override + public BosiumStripReplacementEffect copy() { + return new BosiumStripReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = (Card) game.getState().getValue("BosiumStrip"); + if (card != null) { + controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.STACK, true); + return true; + } + } + 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) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() == Zone.GRAVEYARD) { + Card card = game.getCard(event.getSourceId()); + if (card != null + && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game)) { + CastFromGraveyardWatcher watcher = game.getState().getWatcher(CastFromGraveyardWatcher.class); + return watcher != null + && watcher.spellWasCastFromGraveyard(event.getTargetId(), + game.getState().getZoneChangeCounter(event.getTargetId())); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoundlessRealms.java b/Mage.Sets/src/mage/cards/b/BoundlessRealms.java index bb84c4a6e0..6d8c76a0e6 100644 --- a/Mage.Sets/src/mage/cards/b/BoundlessRealms.java +++ b/Mage.Sets/src/mage/cards/b/BoundlessRealms.java @@ -70,7 +70,7 @@ class BoundlessRealmsEffect extends OneShotEffect { int amount = new PermanentsOnBattlefieldCount(filter).calculate(game, source, this); TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/b/Bribery.java b/Mage.Sets/src/mage/cards/b/Bribery.java index 86e930dfbe..ba5979ca67 100644 --- a/Mage.Sets/src/mage/cards/b/Bribery.java +++ b/Mage.Sets/src/mage/cards/b/Bribery.java @@ -62,7 +62,7 @@ class BriberyEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && opponent != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, new FilterCreatureCard("creature card")); - if (controller.searchLibrary(target, game, opponent.getId())) { + if (controller.searchLibrary(target, source, game, opponent.getId())) { Card card = opponent.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java index 21da232f16..f442083da9 100644 --- a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java +++ b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java @@ -1,4 +1,3 @@ - package mage.cards.b; import java.util.UUID; @@ -13,7 +12,7 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.other.OwnerPredicate; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.events.GameEvent; @@ -28,23 +27,24 @@ import mage.players.Player; */ public final class BridgeFromBelow extends CardImpl { - private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent("Whenever a nontoken creature is put into your graveyard from the battlefield"); private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("When a creature is put into an opponent's graveyard from the battlefield"); - - static{ - filter1.add(new ControllerPredicate(TargetController.YOU)); + + static { + filter1.add(new OwnerPredicate(TargetController.YOU)); filter1.add(Predicates.not(TokenPredicate.instance)); - filter2.add(new ControllerPredicate(TargetController.OPPONENT)); + filter2.add(new OwnerPredicate(TargetController.OPPONENT)); } - + public BridgeFromBelow(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{B}{B}"); // Whenever a nontoken creature is put into your graveyard from the battlefield, if Bridge from Below is in your graveyard, create a 2/2 black Zombie creature token. this.addAbility(new BridgeFromBelowAbility(new CreateTokenEffect(new ZombieToken()), filter1)); + // When a creature is put into an opponent's graveyard from the battlefield, if Bridge from Below is in your graveyard, exile Bridge from Below. this.addAbility(new BridgeFromBelowAbility(new ExileSourceEffect(), filter2)); + } public BridgeFromBelow(final BridgeFromBelow card) { @@ -86,7 +86,8 @@ class BridgeFromBelowAbility extends TriggeredAbilityImpl { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (zEvent.isDiesEvent()) { Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - if (permanent != null && filter.match(permanent, sourceId, controllerId, game)) { + if (permanent != null + && filter.match(permanent, sourceId, controllerId, game)) { return true; } } @@ -96,11 +97,12 @@ class BridgeFromBelowAbility extends TriggeredAbilityImpl { @Override public boolean checkInterveningIfClause(Game game) { Player controller = game.getPlayer(this.getControllerId()); - return controller != null && controller.getGraveyard().contains(this.getSourceId()); + return controller != null + && controller.getGraveyard().contains(this.getSourceId()); } - + @Override public String getRule() { - return filter.getMessage() +", if {this} is in your graveyard, " + super.getRule(); + return filter.getMessage() + ", if {this} is in your graveyard, " + super.getRule(); } } diff --git a/Mage.Sets/src/mage/cards/b/BringToLight.java b/Mage.Sets/src/mage/cards/b/BringToLight.java index 45971d1cd1..1f10c21b5c 100644 --- a/Mage.Sets/src/mage/cards/b/BringToLight.java +++ b/Mage.Sets/src/mage/cards/b/BringToLight.java @@ -74,7 +74,7 @@ class BringToLightEffect extends OneShotEffect { filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.INSTANT), new CardTypePredicate(CardType.SORCERY))); filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, numberColors + 1)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - controller.searchLibrary(target, game); + controller.searchLibrary(target, source, game); Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.EXILED, source, game); diff --git a/Mage.Sets/src/mage/cards/b/BuriedAlive.java b/Mage.Sets/src/mage/cards/b/BuriedAlive.java index fab3129c81..ca43d18192 100644 --- a/Mage.Sets/src/mage/cards/b/BuriedAlive.java +++ b/Mage.Sets/src/mage/cards/b/BuriedAlive.java @@ -61,7 +61,7 @@ class BuriedAliveEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(new CardsImpl(target.getTargets()), Zone.GRAVEYARD, source, game); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/b/BurningProphet.java b/Mage.Sets/src/mage/cards/b/BurningProphet.java index 99085c2af7..1fd95238a1 100644 --- a/Mage.Sets/src/mage/cards/b/BurningProphet.java +++ b/Mage.Sets/src/mage/cards/b/BurningProphet.java @@ -1,5 +1,6 @@ package mage.cards.b; +import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; @@ -12,8 +13,6 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.filter.StaticFilters; -import java.util.UUID; - /** * @author TheElk801 */ @@ -32,7 +31,7 @@ public final class BurningProphet extends CardImpl { new BoostSourceEffect( 1, 0, Duration.EndOfTurn ).setText("{this} gets +1/+0 until end of turn, then"), - StaticFilters.FILTER_SPELL_NON_CREATURE, false + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false ); ability.addEffect(new ScryEffect(1)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/CallousDismissal.java b/Mage.Sets/src/mage/cards/c/CallousDismissal.java new file mode 100644 index 0000000000..db2440421f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CallousDismissal.java @@ -0,0 +1,36 @@ +package mage.cards.c; + +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.keyword.AmassEffect; +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 CallousDismissal extends CardImpl { + + public CallousDismissal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}"); + + // Return target nonland permanent to its owner's hand. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + + // Amass 1. + this.getSpellAbility().addEffect(new AmassEffect(1).concatBy("
")); + } + + private CallousDismissal(final CallousDismissal card) { + super(card); + } + + @Override + public CallousDismissal copy() { + return new CallousDismissal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaravanVigil.java b/Mage.Sets/src/mage/cards/c/CaravanVigil.java index 963e149db3..d915675c4a 100644 --- a/Mage.Sets/src/mage/cards/c/CaravanVigil.java +++ b/Mage.Sets/src/mage/cards/c/CaravanVigil.java @@ -11,7 +11,6 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInLibrary; @@ -63,7 +62,7 @@ class CaravanVigilEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { Cards cards = new CardsImpl(card); diff --git a/Mage.Sets/src/mage/cards/c/CasualtiesOfWar.java b/Mage.Sets/src/mage/cards/c/CasualtiesOfWar.java new file mode 100644 index 0000000000..f6bc983359 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CasualtiesOfWar.java @@ -0,0 +1,62 @@ +package mage.cards.c; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetEnchantmentPermanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CasualtiesOfWar extends CardImpl { + + public CasualtiesOfWar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}{B}{G}{G}"); + + // Choose one or more — + this.getSpellAbility().getModes().setMinModes(1); + this.getSpellAbility().getModes().setMaxModes(5); + + // • Destroy target artifact. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetArtifactPermanent()); + + // • Destroy target creature. + Mode mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addMode(mode); + + // • Destroy target enchantment. + mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetEnchantmentPermanent()); + this.getSpellAbility().addMode(mode); + + // • Destroy target land. + mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetLandPermanent()); + this.getSpellAbility().addMode(mode); + + // • Destroy target planeswalker. + mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_PLANESWALKER)); + this.getSpellAbility().addMode(mode); + } + + private CasualtiesOfWar(final CasualtiesOfWar card) { + super(card); + } + + @Override + public CasualtiesOfWar copy() { + return new CasualtiesOfWar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CentaurNurturer.java b/Mage.Sets/src/mage/cards/c/CentaurNurturer.java new file mode 100644 index 0000000000..e71e56131b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CentaurNurturer.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.mana.AnyColorManaAbility; + + +/** + * + * @author antoni-g + */ +public final class CentaurNurturer extends CardImpl { + + public CentaurNurturer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.CENTAUR); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // When Centaur Nurturer enters the battlefield, you gain 3 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(3))); + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private CentaurNurturer(final CentaurNurturer card) { + super(card); + } + + @Override + public CentaurNurturer copy() { + return new CentaurNurturer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChainwhipCyclops.java b/Mage.Sets/src/mage/cards/c/ChainwhipCyclops.java new file mode 100644 index 0000000000..28551b8860 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChainwhipCyclops.java @@ -0,0 +1,46 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChainwhipCyclops extends CardImpl { + + public ChainwhipCyclops(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.CYCLOPS); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // {3}{R}: Target creature can't block this turn. + Ability ability = new SimpleActivatedAbility( + new CantBlockTargetEffect(Duration.EndOfTurn), new ManaCostsImpl("{3}{R}") + ); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private ChainwhipCyclops(final ChainwhipCyclops card) { + super(card); + } + + @Override + public ChainwhipCyclops copy() { + return new ChainwhipCyclops(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChallengerTroll.java b/Mage.Sets/src/mage/cards/c/ChallengerTroll.java new file mode 100644 index 0000000000..65a3055c7a --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChallengerTroll.java @@ -0,0 +1,80 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChallengerTroll extends CardImpl { + + public ChallengerTroll(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.TROLL); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Each creature you control with power 4 or greater can't be blocked by more than one creature. + this.addAbility(new SimpleStaticAbility(new ChallengerTrollEffect())); + } + + private ChallengerTroll(final ChallengerTroll card) { + super(card); + } + + @Override + public ChallengerTroll copy() { + return new ChallengerTroll(this); + } +} + +class ChallengerTrollEffect extends ContinuousEffectImpl { + + ChallengerTrollEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Each creature you control with power 4 or greater can't be blocked by more than one creature."; + } + + private ChallengerTrollEffect(final ChallengerTrollEffect effect) { + super(effect); + } + + @Override + public ChallengerTrollEffect copy() { + return new ChallengerTrollEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + if (layer != Layer.RulesEffects) { + return false; + } + for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { + if (permanent != null && permanent.isControlledBy(source.getControllerId()) + && permanent.isCreature() && permanent.getPower().getValue() >= 4) { + permanent.setMaxBlockedBy(1); + } + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.RulesEffects; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChandraFireArtisan.java b/Mage.Sets/src/mage/cards/c/ChandraFireArtisan.java new file mode 100644 index 0000000000..052ed7c91f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChandraFireArtisan.java @@ -0,0 +1,136 @@ +package mage.cards.c; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.common.TargetOpponentOrPlaneswalker; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChandraFireArtisan extends CardImpl { + + public ChandraFireArtisan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.CHANDRA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Whenever one or more loyalty counters are removed from Chandra, Fire Artisan, she deals that much damage to target opponent or planeswalker. + this.addAbility(new ChandraFireArtisanTriggeredAbility()); + + // +1: Exile the top card of your library. You may play it this turn. + this.addAbility(new LoyaltyAbility(new ChandraFireArtisanEffect(false), 1)); + + // -7: Exile the top seven cards of your library. You may play them this turn. + this.addAbility(new LoyaltyAbility(new ChandraFireArtisanEffect(true), -7)); + } + + private ChandraFireArtisan(final ChandraFireArtisan card) { + super(card); + } + + @Override + public ChandraFireArtisan copy() { + return new ChandraFireArtisan(this); + } +} + +class ChandraFireArtisanTriggeredAbility extends TriggeredAbilityImpl { + + ChandraFireArtisanTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + this.addTarget(new TargetOpponentOrPlaneswalker()); + } + + private ChandraFireArtisanTriggeredAbility(final ChandraFireArtisanTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_REMOVED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getAmount() == 0 || !event.getData().equals("loyalty") + || !event.getTargetId().equals(getSourceId())) { + return false; + } + this.getEffects().clear(); + this.addEffect(new DamageTargetEffect(event.getAmount())); + return true; + } + + @Override + public ChandraFireArtisanTriggeredAbility copy() { + return new ChandraFireArtisanTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever one or more loyalty counters are removed from {this}, " + + "she deals that much damage to target opponent or planeswalker."; + } +} + +class ChandraFireArtisanEffect extends OneShotEffect { + + private final boolean exileSeven; + + ChandraFireArtisanEffect(boolean exileSeven) { + super(Outcome.Detriment); + this.exileSeven = exileSeven; + if (exileSeven) { + staticText = "Exile the top seven cards of your library. You may play them this turn."; + } else { + staticText = "Exile the top card of your library. You may play it this turn"; + } + } + + private ChandraFireArtisanEffect(final ChandraFireArtisanEffect effect) { + super(effect); + this.exileSeven = effect.exileSeven; + } + + @Override + public ChandraFireArtisanEffect copy() { + return new ChandraFireArtisanEffect(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) { + return false; + } + Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, (exileSeven ? 7 : 1))); + controller.moveCards(cards, Zone.EXILED, source, game); + for (Card card : cards.getCards(game)) { + if (card == null) { + continue; + } + ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/ChandrasPyrohelix.java b/Mage.Sets/src/mage/cards/c/ChandrasPyrohelix.java index 7e31f1a07c..f2520c040c 100644 --- a/Mage.Sets/src/mage/cards/c/ChandrasPyrohelix.java +++ b/Mage.Sets/src/mage/cards/c/ChandrasPyrohelix.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -16,11 +15,11 @@ import mage.target.common.TargetAnyTargetAmount; public final class ChandrasPyrohelix extends CardImpl { public ChandrasPyrohelix(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); - // Chandra's Pyrohelix deals 2 damage divided as you choose among one or two target creatures and/or players. + // Chandra's Pyrohelix deals 2 damage divided as you choose among one or two targets. Effect effect = new DamageMultiEffect(2); - effect.setText("{this} deals 2 damage divided as you choose among one or two target creatures and/or players"); + effect.setText("{this} deals 2 damage divided as you choose among one or two targets"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetAnyTargetAmount(2)); } diff --git a/Mage.Sets/src/mage/cards/c/ChandrasTriumph.java b/Mage.Sets/src/mage/cards/c/ChandrasTriumph.java new file mode 100644 index 0000000000..9ce80e45f7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChandrasTriumph.java @@ -0,0 +1,84 @@ +package mage.cards.c; + +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.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChandrasTriumph extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public ChandrasTriumph(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Chandra's Triumph deals 3 damage to target creature or planeswalker an opponent controls. Chandra's Triumph deals 5 damage to that permanent instead + // if you control a Chandra planeswalker. + this.getSpellAbility().addEffect(new ChandrasTriumphEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private ChandrasTriumph(final ChandrasTriumph card) { + super(card); + } + + @Override + public ChandrasTriumph copy() { + return new ChandrasTriumph(this); + } +} + +class ChandrasTriumphEffect extends OneShotEffect { + + private static final FilterControlledPlaneswalkerPermanent filter + = new FilterControlledPlaneswalkerPermanent(SubType.CHANDRA); + + ChandrasTriumphEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 3 damage to target creature or planeswalker an opponent controls. " + + "{this} deals 5 damage to that permanent instead if you control a Chandra planeswalker."; + } + + private ChandrasTriumphEffect(final ChandrasTriumphEffect effect) { + super(effect); + } + + @Override + public ChandrasTriumphEffect copy() { + return new ChandrasTriumphEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + int damage = 3; + if (!game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game).isEmpty()) { + damage = 5; + } + return permanent.damage(damage, source.getSourceId(), game) > 0; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CharityExtractor.java b/Mage.Sets/src/mage/cards/c/CharityExtractor.java new file mode 100644 index 0000000000..d815a3230f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CharityExtractor.java @@ -0,0 +1,37 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CharityExtractor extends CardImpl { + + public CharityExtractor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(1); + this.toughness = new MageInt(5); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + } + + private CharityExtractor(final CharityExtractor card) { + super(card); + } + + @Override + public CharityExtractor copy() { + return new CharityExtractor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CharmedStray.java b/Mage.Sets/src/mage/cards/c/CharmedStray.java new file mode 100644 index 0000000000..a684da4063 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CharmedStray.java @@ -0,0 +1,56 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.LifelinkAbility; +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.permanent.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CharmedStray extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("other creature you control named Charmed Stray"); + + static { + filter.add(new NamePredicate("Charmed Stray")); + filter.add(AnotherPredicate.instance); + } + + public CharmedStray(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.CAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever Charmed Stray enters the battlefield, put a +1/+1 counter on each other creature you control named Charmed Stray. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter) + )); + } + + private CharmedStray(final CharmedStray card) { + super(card); + } + + @Override + public CharmedStray copy() { + return new CharmedStray(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CitanulFlute.java b/Mage.Sets/src/mage/cards/c/CitanulFlute.java index 1af7222872..de2556d10b 100644 --- a/Mage.Sets/src/mage/cards/c/CitanulFlute.java +++ b/Mage.Sets/src/mage/cards/c/CitanulFlute.java @@ -74,7 +74,7 @@ class CitanulFluteSearchEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = player.getLibrary().getCard(target.getFirstTarget(), game); Cards cards = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/c/ClarionUltimatum.java b/Mage.Sets/src/mage/cards/c/ClarionUltimatum.java index c1528dfa3a..460d287979 100644 --- a/Mage.Sets/src/mage/cards/c/ClarionUltimatum.java +++ b/Mage.Sets/src/mage/cards/c/ClarionUltimatum.java @@ -80,7 +80,7 @@ class ClarionUltimatumEffect extends OneShotEffect { FilterCard filter = new FilterCard("card named " + cardName); filter.add(new NamePredicate(cardName)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { chosenCards.add(card); diff --git a/Mage.Sets/src/mage/cards/c/CollectiveVoyage.java b/Mage.Sets/src/mage/cards/c/CollectiveVoyage.java index 11a0f6ddc9..e027b6eb8b 100644 --- a/Mage.Sets/src/mage/cards/c/CollectiveVoyage.java +++ b/Mage.Sets/src/mage/cards/c/CollectiveVoyage.java @@ -76,7 +76,7 @@ class CollectiveVoyageEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, xSum, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, true, null); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/c/CommandTheDreadhorde.java b/Mage.Sets/src/mage/cards/c/CommandTheDreadhorde.java new file mode 100644 index 0000000000..0858ff2698 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommandTheDreadhorde.java @@ -0,0 +1,79 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CommandTheDreadhorde extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature and/or planeswalker cards in graveyards"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.PLANESWALKER), + new CardTypePredicate(CardType.CREATURE) + )); + } + + public CommandTheDreadhorde(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); + + // Choose any number of target creature and/or planeswalker cards in graveyards. Command the Dreadhorde deals damage to you equal to the total converted mana cost of those cards. Put them onto the battlefield under your control. + this.getSpellAbility().addEffect(new CommandTheDreadhordeEffect()); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(0, Integer.MAX_VALUE, filter)); + } + + private CommandTheDreadhorde(final CommandTheDreadhorde card) { + super(card); + } + + @Override + public CommandTheDreadhorde copy() { + return new CommandTheDreadhorde(this); + } +} + +class CommandTheDreadhordeEffect extends OneShotEffect { + + CommandTheDreadhordeEffect() { + super(Outcome.Benefit); + staticText = "Choose any number of target creature and/or planeswalker cards in graveyards. " + + "{this} deals damage to you equal to the total converted mana cost of those cards. " + + "Put them onto the battlefield under your control."; + } + + private CommandTheDreadhordeEffect(final CommandTheDreadhordeEffect effect) { + super(effect); + } + + @Override + public CommandTheDreadhordeEffect copy() { + return new CommandTheDreadhordeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(source.getTargets().get(0).getTargets()); + int damage = cards.getCards(game).stream().mapToInt(Card::getConvertedManaCost).sum(); + player.damage(damage, source.getSourceId(), game); + return player.moveCards(cards, Zone.BATTLEFIELD, source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CommenceTheEndgame.java b/Mage.Sets/src/mage/cards/c/CommenceTheEndgame.java new file mode 100644 index 0000000000..45eaf89629 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommenceTheEndgame.java @@ -0,0 +1,66 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.CantBeCounteredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.AmassEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CommenceTheEndgame extends CardImpl { + + public CommenceTheEndgame(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}{U}"); + + // This spell can't be countered. + this.addAbility(new CantBeCounteredAbility()); + + // Draw two cards, then amass X, where X is the number of cards in your hand. + this.getSpellAbility().addEffect(new CommenceTheEndgameEffect()); + } + + private CommenceTheEndgame(final CommenceTheEndgame card) { + super(card); + } + + @Override + public CommenceTheEndgame copy() { + return new CommenceTheEndgame(this); + } +} + +class CommenceTheEndgameEffect extends OneShotEffect { + + CommenceTheEndgameEffect() { + super(Outcome.Benefit); + staticText = "Draw two cards, then amass X, where X is the number of cards in your hand."; + } + + private CommenceTheEndgameEffect(final CommenceTheEndgameEffect effect) { + super(effect); + } + + @Override + public CommenceTheEndgameEffect copy() { + return new CommenceTheEndgameEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.drawCards(2, game); + return new AmassEffect(player.getHand().size()).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/Conflux.java b/Mage.Sets/src/mage/cards/c/Conflux.java index 555530b266..a0de7fd3dc 100644 --- a/Mage.Sets/src/mage/cards/c/Conflux.java +++ b/Mage.Sets/src/mage/cards/c/Conflux.java @@ -77,7 +77,7 @@ class ConfluxEffect extends OneShotEffect { TargetCardInLibrary targetGreen = new TargetCardInLibrary(filterGreen); if (you != null && you.getLibrary().hasCards()) { - if (you.searchLibrary(targetWhite, game)) { + if (you.searchLibrary(targetWhite, source, game)) { if (!targetWhite.getTargets().isEmpty()) { for (UUID cardId : targetWhite.getTargets()) { Card card = you.getLibrary().remove(cardId, game); @@ -89,7 +89,7 @@ class ConfluxEffect extends OneShotEffect { } } if (you != null && you.getLibrary().hasCards()) { - if (you.searchLibrary(targetBlue, game)) { + if (you.searchLibrary(targetBlue, source, game)) { if (!targetBlue.getTargets().isEmpty()) { for (UUID cardId : targetBlue.getTargets()) { Card card = you.getLibrary().remove(cardId, game); @@ -101,7 +101,7 @@ class ConfluxEffect extends OneShotEffect { } } if (you != null && you.getLibrary().hasCards()) { - if (you.searchLibrary(targetBlack, game)) { + if (you.searchLibrary(targetBlack, source, game)) { if (!targetBlack.getTargets().isEmpty()) { for (UUID cardId : targetBlack.getTargets()) { Card card = you.getLibrary().remove(cardId, game); @@ -113,7 +113,7 @@ class ConfluxEffect extends OneShotEffect { } } if (you != null && you.getLibrary().hasCards()) { - if (you.searchLibrary(targetRed, game)) { + if (you.searchLibrary(targetRed, source, game)) { if (!targetRed.getTargets().isEmpty()) { for (UUID cardId : targetRed.getTargets()) { Card card = you.getLibrary().remove(cardId, game); @@ -125,7 +125,7 @@ class ConfluxEffect extends OneShotEffect { } } if (you != null && you.getLibrary().hasCards()) { - if (you.searchLibrary(targetGreen, game)) { + if (you.searchLibrary(targetGreen, source, game)) { if (!targetGreen.getTargets().isEmpty()) { for (UUID cardId : targetGreen.getTargets()) { Card card = you.getLibrary().remove(cardId, game); diff --git a/Mage.Sets/src/mage/cards/c/CongregationAtDawn.java b/Mage.Sets/src/mage/cards/c/CongregationAtDawn.java index cdfd7c2e60..6e60b5d15a 100644 --- a/Mage.Sets/src/mage/cards/c/CongregationAtDawn.java +++ b/Mage.Sets/src/mage/cards/c/CongregationAtDawn.java @@ -1,7 +1,6 @@ package mage.cards.c; -import java.util.List; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; @@ -64,7 +63,7 @@ class CongregationAtDawnEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 3, new FilterCreatureCard("creature cards")); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/c/ContagionEngine.java b/Mage.Sets/src/mage/cards/c/ContagionEngine.java index 9a21e1df5f..96c31fe3e6 100644 --- a/Mage.Sets/src/mage/cards/c/ContagionEngine.java +++ b/Mage.Sets/src/mage/cards/c/ContagionEngine.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -21,8 +19,9 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author Loki */ public final class ContagionEngine extends CardImpl { @@ -36,13 +35,13 @@ public final class ContagionEngine extends CardImpl { this.addAbility(ability); // {4}, {T}: Proliferate, then proliferate again. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there. Then do it again.) - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ProliferateEffect(), new GenericManaCost(4)); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ProliferateEffect("", false), new GenericManaCost(4)); ability.addCost(new TapSourceCost()); - ability.addEffect(new ProliferateEffect()); + ability.addEffect(new ProliferateEffect(" again", true).concatBy(", then")); this.addAbility(ability); } - public ContagionEngine(final ContagionEngine card) { + private ContagionEngine(final ContagionEngine card) { super(card); } @@ -60,7 +59,7 @@ class ContagionEngineEffect extends OneShotEffect { staticText = "put a -1/-1 counter on each creature target player controls"; } - ContagionEngineEffect(final ContagionEngineEffect effect) { + private ContagionEngineEffect(final ContagionEngineEffect effect) { super(effect); } diff --git a/Mage.Sets/src/mage/cards/c/ContentiousPlan.java b/Mage.Sets/src/mage/cards/c/ContentiousPlan.java new file mode 100644 index 0000000000..dc9573d558 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ContentiousPlan.java @@ -0,0 +1,33 @@ +package mage.cards.c; + +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author antoni-g + */ +public final class ContentiousPlan extends CardImpl { + + public ContentiousPlan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}"); + + // Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) + this.getSpellAbility().addEffect(new ProliferateEffect()); + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + } + + private ContentiousPlan(final ContentiousPlan card) { + super(card); + } + + @Override + public ContentiousPlan copy() { + return new ContentiousPlan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CoreProwler.java b/Mage.Sets/src/mage/cards/c/CoreProwler.java index 0354d8b176..963b642bbe 100644 --- a/Mage.Sets/src/mage/cards/c/CoreProwler.java +++ b/Mage.Sets/src/mage/cards/c/CoreProwler.java @@ -1,8 +1,5 @@ - - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.effects.common.counter.ProliferateEffect; @@ -12,22 +9,27 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author Loki */ public final class CoreProwler extends CardImpl { - public CoreProwler (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{4}"); + public CoreProwler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); this.subtype.add(SubType.HORROR); this.power = new MageInt(2); this.toughness = new MageInt(2); + + // Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.) this.addAbility(InfectAbility.getInstance()); + + // When Core Prowler dies, proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.addAbility(new DiesTriggeredAbility(new ProliferateEffect())); } - public CoreProwler (final CoreProwler card) { + public CoreProwler(final CoreProwler card) { super(card); } diff --git a/Mage.Sets/src/mage/cards/c/CorpseConnoisseur.java b/Mage.Sets/src/mage/cards/c/CorpseConnoisseur.java index bde105b0f7..d134fd60ac 100644 --- a/Mage.Sets/src/mage/cards/c/CorpseConnoisseur.java +++ b/Mage.Sets/src/mage/cards/c/CorpseConnoisseur.java @@ -70,7 +70,7 @@ class SearchLibraryPutInGraveyard extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/c/CorpseHarvester.java b/Mage.Sets/src/mage/cards/c/CorpseHarvester.java index 9c7efc62df..0735a84c4a 100644 --- a/Mage.Sets/src/mage/cards/c/CorpseHarvester.java +++ b/Mage.Sets/src/mage/cards/c/CorpseHarvester.java @@ -82,7 +82,7 @@ class CorpseHarvesterEffect extends OneShotEffect { FilterCard filter = new FilterCard(subtype); filter.add(new SubtypePredicate(SubType.byDescription(subtype))); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { card.moveToZone(Zone.HAND, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/c/CorpsejackMenace.java b/Mage.Sets/src/mage/cards/c/CorpsejackMenace.java index cb0f555675..5032f414fc 100644 --- a/Mage.Sets/src/mage/cards/c/CorpsejackMenace.java +++ b/Mage.Sets/src/mage/cards/c/CorpsejackMenace.java @@ -1,29 +1,25 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; 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.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * http://www.wizards.com/magic/magazine/article.aspx?x=mtg/faq/rtr - * + *

* If a creature you control would enter the battlefield with a number of +1/+1 * counters on it, it enters with twice that many instead. - * + *

* If you control two Corpsejack Menaces, the number of +1/+1 counters placed is * four times the original number. Three Corpsejack Menaces multiplies the * original number by eight, and so on. @@ -33,7 +29,7 @@ import mage.game.permanent.Permanent; public final class CorpsejackMenace extends CardImpl { public CorpsejackMenace(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}"); this.subtype.add(SubType.FUNGUS); this.power = new MageInt(4); @@ -67,7 +63,7 @@ class CorpsejackMenaceReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() * 2); + event.setAmountForCounters(event.getAmount() * 2, true); return false; } @@ -78,15 +74,13 @@ class CorpsejackMenaceReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getData().equals(CounterType.P1P1.getName())) { + if (event.getData().equals(CounterType.P1P1.getName()) && event.getAmount() > 0) { Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent == null) { permanent = game.getPermanentEntering(event.getTargetId()); } - if (permanent != null && permanent.isControlledBy(source.getControllerId()) - && permanent.isCreature()) { - return true; - } + return permanent != null && permanent.isControlledBy(source.getControllerId()) + && permanent.isCreature(); } return false; } diff --git a/Mage.Sets/src/mage/cards/c/CourageInCrisis.java b/Mage.Sets/src/mage/cards/c/CourageInCrisis.java index 5df69ea249..9b10852d11 100644 --- a/Mage.Sets/src/mage/cards/c/CourageInCrisis.java +++ b/Mage.Sets/src/mage/cards/c/CourageInCrisis.java @@ -18,7 +18,7 @@ public final class CourageInCrisis extends CardImpl { public CourageInCrisis(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); - // Put a +1/+1 counter on target creature, then proliferate. + // Put a +1/+1 counter on target creature, then proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); this.getSpellAbility().addEffect(new ProliferateEffect().concatBy(", then")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/c/CrovaxTheCursed.java b/Mage.Sets/src/mage/cards/c/CrovaxTheCursed.java index a1f7a602a2..a7598d5058 100644 --- a/Mage.Sets/src/mage/cards/c/CrovaxTheCursed.java +++ b/Mage.Sets/src/mage/cards/c/CrovaxTheCursed.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -22,14 +20,15 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class CrovaxTheCursed extends CardImpl { public CrovaxTheCursed(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.VAMPIRE); this.power = new MageInt(0); @@ -82,7 +81,7 @@ class CrovaxTheCursedEffect extends OneShotEffect { if (creatures > 0 && controller.chooseUse(outcome, "Sacrifice a creature?", source, game)) { if (new SacrificeControllerEffect(StaticFilters.FILTER_PERMANENT_CREATURES, 1, "").apply(game, source)) { if (sourceObject != null) { - sourceObject.getCounters(game).addCounter(CounterType.P1P1.createInstance()); + sourceObject.addCounters(CounterType.P1P1.createInstance(), source, game); game.informPlayers(controller.getLogName() + " puts a +1/+1 counter on " + sourceObject.getName()); } } diff --git a/Mage.Sets/src/mage/cards/c/Cultivate.java b/Mage.Sets/src/mage/cards/c/Cultivate.java index 1905336011..26e42118d3 100644 --- a/Mage.Sets/src/mage/cards/c/Cultivate.java +++ b/Mage.Sets/src/mage/cards/c/Cultivate.java @@ -67,7 +67,7 @@ class CultivateEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(target.getTargets()); controller.revealCards(sourceObject.getIdName(), revealed, game); diff --git a/Mage.Sets/src/mage/cards/c/CurseOfMisfortunes.java b/Mage.Sets/src/mage/cards/c/CurseOfMisfortunes.java index 2e781e9c55..1fa8220d2f 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfMisfortunes.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfMisfortunes.java @@ -87,7 +87,7 @@ class CurseOfMisfortunesEffect extends OneShotEffect { } } TargetCardInLibrary targetCard = new TargetCardInLibrary(filter); - if (player.searchLibrary(targetCard, game)) { + if (player.searchLibrary(targetCard, source, game)) { Card card = game.getCard(targetCard.getFirstTarget()); if (card != null) { this.setTargetPointer(new FixedTarget(targetPlayer.getId())); diff --git a/Mage.Sets/src/mage/cards/c/CurseOfPredation.java b/Mage.Sets/src/mage/cards/c/CurseOfPredation.java index a130d37685..7616a3cc56 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfPredation.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfPredation.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -29,10 +28,9 @@ import mage.target.targetpointer.FixedTarget; public final class CurseOfPredation extends CardImpl { public CurseOfPredation(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); this.subtype.add(SubType.AURA, SubType.CURSE); - // Enchant player TargetPlayer auraTarget = new TargetPlayer(); this.getSpellAbility().addTarget(auraTarget); @@ -75,17 +73,11 @@ class CurseOfPredationTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Player defender = game.getPlayer(event.getTargetId()); - if (defender == null) { - Permanent planeswalker = game.getPermanent(event.getTargetId()); - if (planeswalker != null) { - defender = game.getPlayer(planeswalker.getControllerId()); - } - } if (defender != null) { Permanent enchantment = game.getPermanent(this.getSourceId()); if (enchantment != null && enchantment.isAttachedTo(defender.getId())) { - for (Effect effect: this.getEffects()) { + for (Effect effect : this.getEffects()) { effect.setTargetPointer(new FixedTarget(event.getSourceId())); } return true; diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java b/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java index 18040ebb79..1d12b1daf5 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.Ability; @@ -29,16 +28,16 @@ import java.util.UUID; public final class CurseOfTheBloodyTome extends CardImpl { public CurseOfTheBloodyTome(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); this.subtype.add(SubType.AURA, SubType.CURSE); - // Enchant player TargetPlayer target = new TargetPlayer(); this.getSpellAbility().addTarget(target); this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); Ability ability = new EnchantAbility(target.getTargetName()); this.addAbility(ability); + // At the beginning of enchanted player's upkeep, that player puts the top two cards of their library into their graveyard. this.addAbility(new CurseOfTheBloodyTomeAbility()); diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java b/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java index 8e2ab315d4..0c4554afc9 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.Ability; @@ -42,10 +41,13 @@ public final class CurseOfTheCabal extends CardImpl { // Target player sacrifices half the permanents he or she controls, rounded down. this.getSpellAbility().addTarget(new TargetPlayer()); this.getSpellAbility().addEffect(new CurseOfTheCabalSacrificeEffect()); + // Suspend 2-{2}{B}{B} this.addAbility(new SuspendAbility(2, new ManaCostsImpl("{2}{B}{B}"), this)); + // At the beginning of each player's upkeep, if Curse of the Cabal is suspended, that player may sacrifice a permanent. If he or she does, put two time counters on Curse of the Cabal. this.addAbility(new CurseOfTheCabalInterveningIfTriggeredAbility()); + } public CurseOfTheCabal(final CurseOfTheCabal card) { @@ -84,7 +86,8 @@ class CurseOfTheCabalSacrificeEffect extends OneShotEffect { } Target target = new TargetControlledPermanent(amount, amount, StaticFilters.FILTER_CONTROLLED_PERMANENT, true); if (target.canChoose(targetPlayer.getId(), game)) { - while (!target.isChosen() && target.canChoose(targetPlayer.getId(), game) && targetPlayer.canRespond()) { + while (!target.isChosen() + && target.canChoose(targetPlayer.getId(), game) && targetPlayer.canRespond()) { targetPlayer.choose(Outcome.Sacrifice, target, source.getSourceId(), game); } //sacrifice all chosen (non null) permanents @@ -107,7 +110,9 @@ class CurseOfTheCabalInterveningIfTriggeredAbility extends ConditionalIntervenin TargetController.ANY, false, true ), SuspendedCondition.instance, - "At the beginning of each player's upkeep, if {this} is suspended, that player may sacrifice a permanent. If he or she does, put two time counters on {this}." + "At the beginning of each player's upkeep, if {this} is suspended, " + + "that player may sacrifice a permanent. If he or she does, " + + "put two time counters on {this}." ); // controller has to sac a permanent // counters aren't placed diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheForsaken.java b/Mage.Sets/src/mage/cards/c/CurseOfTheForsaken.java index f240a0e5d6..ee09485bc1 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheForsaken.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheForsaken.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.TriggeredAbilityImpl; @@ -29,7 +28,7 @@ import java.util.UUID; public final class CurseOfTheForsaken extends CardImpl { public CurseOfTheForsaken(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); this.subtype.add(SubType.AURA, SubType.CURSE); // Enchant player @@ -40,6 +39,7 @@ public final class CurseOfTheForsaken extends CardImpl { // Whenever a creature attacks enchanted player, its controller gains 1 life. this.addAbility(new CurseOfTheForsakenTriggeredAbility()); + } public CurseOfTheForsaken(final CurseOfTheForsaken card) { @@ -74,12 +74,6 @@ class CurseOfTheForsakenTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Player defender = game.getPlayer(event.getTargetId()); - if (defender == null) { - Permanent planeswalker = game.getPermanent(event.getTargetId()); - if (planeswalker != null) { - defender = game.getPlayer(planeswalker.getControllerId()); - } - } if (defender != null) { Permanent enchantment = game.getPermanent(this.getSourceId()); if (enchantment != null diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java b/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java index 6332c7987e..ece104889a 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.Ability; @@ -29,8 +28,10 @@ public final class CurseOfTheSwine extends CardImpl { // Exile X target creatures. For each creature exiled this way, its controller creates a 2/2 green Boar creature token. this.getSpellAbility().addEffect(new CurseOfTheSwineEffect()); + // Correct number of targets will be set in adjustTargets this.getSpellAbility().setTargetAdjuster(CurseOfTheSwineAdjuster.instance); + } public CurseOfTheSwine(final CurseOfTheSwine card) { @@ -57,7 +58,8 @@ class CurseOfTheSwineEffect extends OneShotEffect { public CurseOfTheSwineEffect() { super(Outcome.Exile); - this.staticText = "Exile X target creatures. For each creature exiled this way, its controller creates a 2/2 green Boar creature token"; + this.staticText = "Exile X target creatures. For each creature exiled this way, " + + "its controller creates a 2/2 green Boar creature token"; } public CurseOfTheSwineEffect(final CurseOfTheSwineEffect effect) { @@ -78,13 +80,15 @@ class CurseOfTheSwineEffect extends OneShotEffect { Permanent creature = game.getPermanent(targetId); if (creature != null) { if (controller.moveCards(creature, Zone.EXILED, source, game)) { - playersWithTargets.put(creature.getControllerId(), playersWithTargets.getOrDefault(creature.getControllerId(), 0) + 1); + playersWithTargets.put(creature.getControllerId(), + playersWithTargets.getOrDefault(creature.getControllerId(), 0) + 1); } } } CurseOfTheSwineBoarToken swineToken = new CurseOfTheSwineBoarToken(); for (Map.Entry exiledByController : playersWithTargets.entrySet()) { - swineToken.putOntoBattlefield(exiledByController.getValue(), game, source.getSourceId(), exiledByController.getKey()); + swineToken.putOntoBattlefield(exiledByController.getValue(), + game, source.getSourceId(), exiledByController.getKey()); } return true; } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfThirst.java b/Mage.Sets/src/mage/cards/c/CurseOfThirst.java index 97c1c9c1ea..05e564079e 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfThirst.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfThirst.java @@ -1,4 +1,3 @@ - package mage.cards.c; import mage.abilities.Ability; @@ -31,10 +30,9 @@ import java.util.UUID; public final class CurseOfThirst extends CardImpl { public CurseOfThirst(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}"); this.subtype.add(SubType.AURA, SubType.CURSE); - // Enchant player TargetPlayer auraTarget = new TargetPlayer(); this.getSpellAbility().addTarget(auraTarget); @@ -91,7 +89,8 @@ class CurseOfThirstAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "At the beginning of enchanted player's upkeep, Curse of Thirst deals damage to that player equal to the number of Curses attached to him or her."; + return "At the beginning of enchanted player's upkeep, Curse of Thirst " + + "deals damage to that player equal to the number of Curses attached to him or her."; } } @@ -108,10 +107,11 @@ class CursesAttachedCount implements DynamicValue { if (enchantment != null && enchantment.getAttachedTo() != null) { Player player = game.getPlayer(enchantment.getAttachedTo()); if (player != null) { - for (UUID attachmentId: player.getAttachments()) { + for (UUID attachmentId : player.getAttachments()) { Permanent attachment = game.getPermanent(attachmentId); - if (attachment != null && attachment.hasSubtype(SubType.CURSE, game)) + if (attachment != null && attachment.hasSubtype(SubType.CURSE, game)) { count++; + } } } } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfVengeance.java b/Mage.Sets/src/mage/cards/c/CurseOfVengeance.java index 318a99d37e..e12fb3e6da 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfVengeance.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfVengeance.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -130,7 +129,8 @@ class CurseOfVengeancePlayerLosesTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "When enchanted player loses the game, you gain X life and draw X cards, where X is the number of spite counters on {this}"; + return "When enchanted player loses the game, you gain X life and " + + "draw X cards, where X is the number of spite counters on {this}"; } } @@ -138,7 +138,8 @@ class CurseOfVengeanceDrawLifeEffect extends OneShotEffect { public CurseOfVengeanceDrawLifeEffect() { super(Outcome.Benefit); - staticText = "you gain X life and draw X cards, where X is the number of spite counters on {this}"; + staticText = "you gain X life and draw X cards, where X is the " + + "number of spite counters on {this}"; } public CurseOfVengeanceDrawLifeEffect(final CurseOfVengeanceDrawLifeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/d/DarkDecision.java b/Mage.Sets/src/mage/cards/d/DarkDecision.java index f89c73413b..cd96221c60 100644 --- a/Mage.Sets/src/mage/cards/d/DarkDecision.java +++ b/Mage.Sets/src/mage/cards/d/DarkDecision.java @@ -69,7 +69,7 @@ class DarkDecisionEffect extends OneShotEffect { if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(new FilterNonlandCard()); target.setCardLimit(10); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { UUID targetId = target.getFirstTarget(); Card card = game.getCard(targetId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/d/DarkSupplicant.java b/Mage.Sets/src/mage/cards/d/DarkSupplicant.java index f733d39b2f..387bc27557 100644 --- a/Mage.Sets/src/mage/cards/d/DarkSupplicant.java +++ b/Mage.Sets/src/mage/cards/d/DarkSupplicant.java @@ -110,7 +110,7 @@ class DarkSupplicantEffect extends OneShotEffect { && controller.chooseUse(Outcome.Benefit, "Do you want to search your library for Scion of Darkness?", source, game)) { librarySearched = true; TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { selectedCard = game.getCard(target.getFirstTarget()); } diff --git a/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java b/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java index 18839aaa6a..6fc58b4f82 100644 --- a/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java +++ b/Mage.Sets/src/mage/cards/d/DarthTyranusCountOfSerenno.java @@ -124,7 +124,7 @@ class TransmuteArtifactEffect extends SearchEffect { sacrifice = permanent.sacrifice(source.getSourceId(), game); } } - if (sacrifice && controller.searchLibrary(target, game)) { + if (sacrifice && controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { Card card = controller.getLibrary().getCard(cardId, game); diff --git a/Mage.Sets/src/mage/cards/d/DawnbreakReclaimer.java b/Mage.Sets/src/mage/cards/d/DawnbreakReclaimer.java index b3f06e02e7..1f45bb9d57 100644 --- a/Mage.Sets/src/mage/cards/d/DawnbreakReclaimer.java +++ b/Mage.Sets/src/mage/cards/d/DawnbreakReclaimer.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.HashSet; @@ -20,10 +19,11 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.other.OwnerIdPredicate; +import mage.filter.predicate.other.OwnerPredicate; import mage.game.Game; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInGraveyard; -import mage.target.common.TargetCardInOpponentsGraveyard; import mage.target.common.TargetOpponent; /** @@ -33,7 +33,7 @@ import mage.target.common.TargetOpponent; public final class DawnbreakReclaimer extends CardImpl { public DawnbreakReclaimer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); this.subtype.add(SubType.ANGEL); this.power = new MageInt(5); this.toughness = new MageInt(5); @@ -74,11 +74,10 @@ class DawnbreakReclaimerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { /** - * 04.11.2015 If any opponent has a creature card in their - * graveyard as Dawnbreak Reclaimer's ability resolves, then you must - * choose one of those cards. You can't choose a different opponent with - * no creature cards in their graveyard to avoid returning one of - * those cards. + * 04.11.2015 If any opponent has a creature card in their graveyard as + * Dawnbreak Reclaimer's ability resolves, then you must choose one of + * those cards. You can't choose a different opponent with no creature + * cards in their graveyard to avoid returning one of those cards. * * 04.11.2015 If there are no creature cards in any opponent's graveyard * as Dawnbreak Reclaimer's ability resolves, you'll still have the @@ -88,16 +87,24 @@ class DawnbreakReclaimerEffect extends OneShotEffect { */ Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); - if (controller != null && sourceObject != null) { - TargetCardInOpponentsGraveyard targetOpponentGraveyard = new TargetCardInOpponentsGraveyard(new FilterCreatureCard("a creature card in an opponent's graveyard")); + if (controller != null + && sourceObject != null) { + FilterCreatureCard filter = new FilterCreatureCard("a creature card in an opponent's graveyard"); + filter.add(new OwnerPredicate(TargetController.OPPONENT)); + TargetCard chosenCreatureOpponentGraveyard = new TargetCard(Zone.GRAVEYARD, filter); Player opponent = null; Card cardOpponentGraveyard = null; - if (targetOpponentGraveyard.canChoose(source.getSourceId(), source.getControllerId(), game)) { - controller.choose(Outcome.Detriment, targetOpponentGraveyard, source.getSourceId(), game); - cardOpponentGraveyard = game.getCard(targetOpponentGraveyard.getFirstTarget()); + chosenCreatureOpponentGraveyard.setNotTarget(true); + if (chosenCreatureOpponentGraveyard.canChoose(source.getSourceId(), source.getControllerId(), game)) { + controller.choose(Outcome.Detriment, chosenCreatureOpponentGraveyard, source.getSourceId(), game); + cardOpponentGraveyard = game.getCard(chosenCreatureOpponentGraveyard.getFirstTarget()); if (cardOpponentGraveyard != null) { opponent = game.getPlayer(cardOpponentGraveyard.getOwnerId()); - game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " has chosen " + cardOpponentGraveyard.getIdName() + " of " + opponent.getLogName()); + game.informPlayers(sourceObject.getLogName() + + ": " + controller.getLogName() + + " has chosen " + + cardOpponentGraveyard.getIdName() + + " of " + opponent.getLogName()); } } if (opponent == null) { @@ -106,20 +113,29 @@ class DawnbreakReclaimerEffect extends OneShotEffect { controller.choose(outcome, targetOpponent, source.getSourceId(), game); opponent = game.getPlayer(targetOpponent.getFirstTarget()); if (opponent != null) { - game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " has chosen " + opponent.getLogName() + " to select a creature card from their graveyard"); + game.informPlayers(sourceObject.getLogName() + + ": " + controller.getLogName() + + " has chosen " + + opponent.getLogName() + + " to select a creature card from their graveyard"); } } if (opponent != null) { - FilterCreatureCard filter = new FilterCreatureCard("a creature card in " + controller.getName() + "'s the graveyard"); - filter.add(new OwnerIdPredicate(controller.getId())); - TargetCardInGraveyard targetControllerGaveyard = new TargetCardInGraveyard(filter); + FilterCreatureCard filterCreatureCard = + new FilterCreatureCard("a creature card in " + controller.getName() + "'s the graveyard"); + filterCreatureCard.add(new OwnerIdPredicate(controller.getId())); + TargetCardInGraveyard targetControllerGaveyard = new TargetCardInGraveyard(filterCreatureCard); targetControllerGaveyard.setNotTarget(true); Card controllerCreatureCard = null; if (targetControllerGaveyard.canChoose(source.getSourceId(), opponent.getId(), game) && opponent.choose(outcome, targetControllerGaveyard, source.getSourceId(), game)) { controllerCreatureCard = game.getCard(targetControllerGaveyard.getFirstTarget()); if (controllerCreatureCard != null) { - game.informPlayers(sourceObject.getLogName() + ": " + opponent.getLogName() + " has chosen " + controllerCreatureCard.getIdName() + " of " + controller.getLogName()); + game.informPlayers(sourceObject.getLogName() + + ": " + opponent.getLogName() + + " has chosen " + + controllerCreatureCard.getIdName() + + " of " + controller.getLogName()); } } Set cards = new HashSet<>(); @@ -133,7 +149,11 @@ class DawnbreakReclaimerEffect extends OneShotEffect { if (controller.chooseUse( outcome, "Return those cards to the battlefield under their owners' control?", - "Opponent's creature card: " + (cardOpponentGraveyard == null ? "none" : cardOpponentGraveyard.getLogName()) + ", your creature card: " + (controllerCreatureCard == null ? "none" : controllerCreatureCard.getLogName()), + "Opponent's creature card: " + + (cardOpponentGraveyard == null + ? "none" : cardOpponentGraveyard.getLogName()) + + ", your creature card: " + (controllerCreatureCard == null + ? "none" : controllerCreatureCard.getLogName()), null, null, source, diff --git a/Mage.Sets/src/mage/cards/d/DeliverUntoEvil.java b/Mage.Sets/src/mage/cards/d/DeliverUntoEvil.java new file mode 100644 index 0000000000..dd8cf7be8b --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeliverUntoEvil.java @@ -0,0 +1,102 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +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.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeliverUntoEvil extends CardImpl { + + public DeliverUntoEvil(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Choose up to four target cards in your graveyard. If you control a Bolas planeswalker, return those cards to your hand. Otherwise, an opponent chooses two of them. Leave the chosen cards in your graveyard and put the rest into your hand. + this.getSpellAbility().addEffect(new DeliverUntoEvilEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 4)); + + // Exile Deliver Unto Evil. + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + private DeliverUntoEvil(final DeliverUntoEvil card) { + super(card); + } + + @Override + public DeliverUntoEvil copy() { + return new DeliverUntoEvil(this); + } +} + +class DeliverUntoEvilEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPlaneswalkerPermanent(SubType.BOLAS); + private static final FilterCard filter2 = new FilterCard("cards (to leave in the graveyard)"); + + DeliverUntoEvilEffect() { + super(Outcome.Benefit); + staticText = "Choose up to four target cards in your graveyard. If you control a Bolas planeswalker, " + + "return those cards to your hand. Otherwise, an opponent chooses two of them. " + + "Leave the chosen cards in your graveyard and put the rest into your hand.
"; + } + + private DeliverUntoEvilEffect(final DeliverUntoEvilEffect effect) { + super(effect); + } + + @Override + public DeliverUntoEvilEffect copy() { + return new DeliverUntoEvilEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(source.getTargets().get(0).getTargets()); + if (cards.isEmpty()) { + return false; + } + if (!game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game).isEmpty()) { + return player.moveCards(cards, Zone.HAND, source, game); + } + TargetOpponent targetOpponent = new TargetOpponent(); + targetOpponent.setNotTarget(true); + if (!player.choose(outcome, targetOpponent, source.getSourceId(), game)) { + return false; + } + Player opponent = game.getPlayer(targetOpponent.getFirstTarget()); + if (opponent == null) { + return false; + } + TargetCard targetCard = new TargetCardInGraveyard(Math.min(2, cards.size()), filter2); + if (!opponent.choose(outcome, cards, targetCard, game)) { + return false; + } + cards.removeAll(targetCard.getTargets()); + return player.moveCards(cards, Zone.HAND, source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DenyingWind.java b/Mage.Sets/src/mage/cards/d/DenyingWind.java index e371317b50..9830baaae6 100644 --- a/Mage.Sets/src/mage/cards/d/DenyingWind.java +++ b/Mage.Sets/src/mage/cards/d/DenyingWind.java @@ -63,7 +63,7 @@ class DenyingWindEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 7, new FilterCard("cards from player's library to exile")); - if (controller.searchLibrary(target, game, player.getId())) { + if (controller.searchLibrary(target, source, game, player.getId())) { List targets = target.getTargets(); for (UUID targetId : targets) { Card card = player.getLibrary().remove(targetId, game); diff --git a/Mage.Sets/src/mage/cards/d/Despark.java b/Mage.Sets/src/mage/cards/d/Despark.java new file mode 100644 index 0000000000..cca3d6bcae --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Despark.java @@ -0,0 +1,42 @@ +package mage.cards.d; + +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Despark extends CardImpl { + + private static final FilterPermanent filter + = new FilterPermanent("permanent with converted mana cost 4 or greater"); + + static { + filter.add(new ConvertedManaCostPredicate(ComparisonType.MORE_THAN, 3)); + } + + public Despark(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}{B}"); + + // Exile target permanent with converted mana cost 4 or greater. + this.getSpellAbility().addEffect(new ExileTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private Despark(final Despark card) { + super(card); + } + + @Override + public Despark copy() { + return new Despark(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesperateLunge.java b/Mage.Sets/src/mage/cards/d/DesperateLunge.java new file mode 100644 index 0000000000..ba5f659bde --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesperateLunge.java @@ -0,0 +1,42 @@ +package mage.cards.d; + +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesperateLunge extends CardImpl { + + public DesperateLunge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Target creature gets +2/+2 and gains flying until end of turn. You gain 2 life. + this.getSpellAbility().addEffect(new BoostTargetEffect( + 2, 2, Duration.EndOfTurn + ).setText("Target creature gets +2/+2")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains flying until end of turn.")); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private DesperateLunge(final DesperateLunge card) { + super(card); + } + + @Override + public DesperateLunge copy() { + return new DesperateLunge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DetectionTower.java b/Mage.Sets/src/mage/cards/d/DetectionTower.java index 3f0ec08994..6737f549a0 100644 --- a/Mage.Sets/src/mage/cards/d/DetectionTower.java +++ b/Mage.Sets/src/mage/cards/d/DetectionTower.java @@ -72,12 +72,12 @@ class DetectionTowerEffect extends AsThoughEffectImpl { } @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - if (game.getOpponents(source.getControllerId()).contains(sourceId)) { + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId())) { // + if (game.getOpponents(source.getControllerId()).contains(objectId)) { return true; } - Permanent creature = game.getPermanent(sourceId); + Permanent creature = game.getPermanent(objectId); if (creature != null && game.getOpponents(source.getControllerId()).contains(creature.getControllerId())) { return true; diff --git a/Mage.Sets/src/mage/cards/d/DiabolicRevelation.java b/Mage.Sets/src/mage/cards/d/DiabolicRevelation.java index bdac351e7b..40b7ec3379 100644 --- a/Mage.Sets/src/mage/cards/d/DiabolicRevelation.java +++ b/Mage.Sets/src/mage/cards/d/DiabolicRevelation.java @@ -66,7 +66,7 @@ class DiabolicRevelationEffect extends OneShotEffect { return false; } - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().remove(cardId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/d/Dichotomancy.java b/Mage.Sets/src/mage/cards/d/Dichotomancy.java index a2bcbcadb8..dd099831c3 100644 --- a/Mage.Sets/src/mage/cards/d/Dichotomancy.java +++ b/Mage.Sets/src/mage/cards/d/Dichotomancy.java @@ -4,21 +4,15 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.mageobject.NamePredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.filter.predicate.permanent.ControllerPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -81,7 +75,7 @@ class DichotomancyEffect extends OneShotEffect { FilterCard filterCard = new FilterCard("card named \""+name+'"'); filterCard.add(new NamePredicate(name)); TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filterCard); - if (controller.searchLibrary(target, game, opponent.getId())) { + if (controller.searchLibrary(target, source, game, opponent.getId())) { controller.moveCards(opponent.getLibrary().getCard(target.getFirstTarget(), game), Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/d/DiscoveryDispersal.java b/Mage.Sets/src/mage/cards/d/DiscoveryDispersal.java index 6ef69bb789..ac6b357b5d 100644 --- a/Mage.Sets/src/mage/cards/d/DiscoveryDispersal.java +++ b/Mage.Sets/src/mage/cards/d/DiscoveryDispersal.java @@ -1,8 +1,5 @@ package mage.cards.d; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; @@ -12,11 +9,7 @@ import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; import mage.abilities.effects.keyword.SurveilEffect; import mage.cards.CardSetInfo; import mage.cards.SplitCard; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.Outcome; -import mage.constants.SpellAbilityType; -import mage.constants.TargetController; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.Predicates; @@ -29,8 +22,11 @@ import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class DiscoveryDispersal extends SplitCard { @@ -57,7 +53,7 @@ public final class DiscoveryDispersal extends SplitCard { this.getRightHalfCard().getSpellAbility().addEffect(new DispersalEffect()); } - public DiscoveryDispersal(final DiscoveryDispersal card) { + private DiscoveryDispersal(final DiscoveryDispersal card) { super(card); } @@ -69,7 +65,7 @@ public final class DiscoveryDispersal extends SplitCard { class DispersalEffect extends OneShotEffect { - public DispersalEffect() { + DispersalEffect() { super(Outcome.Benefit); this.staticText = "Each opponent returns a nonland permanent " + "they control with the highest converted mana cost " @@ -77,7 +73,7 @@ class DispersalEffect extends OneShotEffect { + "then discards a card."; } - public DispersalEffect(final DispersalEffect effect) { + private DispersalEffect(final DispersalEffect effect) { super(effect); } @@ -100,7 +96,7 @@ class DispersalEffect extends OneShotEffect { } int highestCMC = 0; for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponentId)) { - if (permanent != null && !permanent.isLand()) { + if (permanent != null) { highestCMC = Math.max(highestCMC, permanent.getConvertedManaCost()); } } diff --git a/Mage.Sets/src/mage/cards/d/DistantMemories.java b/Mage.Sets/src/mage/cards/d/DistantMemories.java index 59c52e4c80..68d8ffe9e7 100644 --- a/Mage.Sets/src/mage/cards/d/DistantMemories.java +++ b/Mage.Sets/src/mage/cards/d/DistantMemories.java @@ -62,7 +62,7 @@ class DistantMemoriesEffect extends OneShotEffect { } TargetCardInLibrary target = new TargetCardInLibrary(); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { card.moveToZone(Zone.EXILED, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/d/DivineArrow.java b/Mage.Sets/src/mage/cards/d/DivineArrow.java new file mode 100644 index 0000000000..11464bc56b --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DivineArrow.java @@ -0,0 +1,32 @@ +package mage.cards.d; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetAttackingOrBlockingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DivineArrow extends CardImpl { + + public DivineArrow(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Divine Arrow deals 4 damage to target attacking or blocking creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(4)); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + } + + private DivineArrow(final DivineArrow card) { + super(card); + } + + @Override + public DivineArrow copy() { + return new DivineArrow(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DomriAnarchOfBolas.java b/Mage.Sets/src/mage/cards/d/DomriAnarchOfBolas.java new file mode 100644 index 0000000000..e6fda3bc43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DomriAnarchOfBolas.java @@ -0,0 +1,102 @@ +package mage.cards.d; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CantBeCounteredControlledEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DomriAnarchOfBolas extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature you don't control"); + + static { + filter.add(new ControllerPredicate(TargetController.NOT_YOU)); + } + + public DomriAnarchOfBolas(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{R}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DOMRI); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(3)); + + // Creatures you control get +1/+0. + this.addAbility(new SimpleStaticAbility( + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield) + )); + + // +1: Add {R} or {G}. Creature spells you cast this turn can't be countered. + this.addAbility(new LoyaltyAbility(new DomriAnarchOfBolasEffect(), 1)); + + // -2: Target creature you control fights target creature you don't control. + Ability ability = new LoyaltyAbility(new FightTargetsEffect(), -2); + ability.addTarget(new TargetControlledCreaturePermanent()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private DomriAnarchOfBolas(final DomriAnarchOfBolas card) { + super(card); + } + + @Override + public DomriAnarchOfBolas copy() { + return new DomriAnarchOfBolas(this); + } +} + +class DomriAnarchOfBolasEffect extends OneShotEffect { + + DomriAnarchOfBolasEffect() { + super(Outcome.Benefit); + staticText = "Add {R} or {G}. Creature spells you cast this turn can't be countered."; + } + + private DomriAnarchOfBolasEffect(final DomriAnarchOfBolasEffect effect) { + super(effect); + } + + @Override + public DomriAnarchOfBolasEffect copy() { + return new DomriAnarchOfBolasEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Mana mana = new Mana(); + if (player.chooseUse(outcome, "Choose a color of mana to add", null, "Red", "Green", source, game)) { + mana.increaseRed(); + } else { + mana.increaseGreen(); + } + player.getManaPool().addMana(mana, game, source); + game.addEffect(new CantBeCounteredControlledEffect(StaticFilters.FILTER_SPELL_A_CREATURE, Duration.EndOfTurn), source); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DomrisAmbush.java b/Mage.Sets/src/mage/cards/d/DomrisAmbush.java new file mode 100644 index 0000000000..ba5012fd40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DomrisAmbush.java @@ -0,0 +1,81 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageWithPowerTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DomrisAmbush extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker you don't control"); + + static { + filter.add(new ControllerPredicate(TargetController.NOT_YOU)); + } + + public DomrisAmbush(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{G}"); + + // Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control. + this.getSpellAbility().addEffect(new DomrisAmbushEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private DomrisAmbush(final DomrisAmbush card) { + super(card); + } + + @Override + public DomrisAmbush copy() { + return new DomrisAmbush(this); + } +} + +class DomrisAmbushEffect extends OneShotEffect { + + DomrisAmbushEffect() { + super(Outcome.Benefit); + staticText = "Put a +1/+1 counter on target creature you control. " + + "Then that creature deals damage equal to its power " + + "to target creature or planeswalker you don't control."; + } + + private DomrisAmbushEffect(final DomrisAmbushEffect effect) { + super(effect); + } + + @Override + public DomrisAmbushEffect copy() { + return new DomrisAmbushEffect(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); + game.applyEffects(); + return new DamageWithPowerTargetEffect().apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DoublingChant.java b/Mage.Sets/src/mage/cards/d/DoublingChant.java index 2bc93750d2..1a459bce34 100644 --- a/Mage.Sets/src/mage/cards/d/DoublingChant.java +++ b/Mage.Sets/src/mage/cards/d/DoublingChant.java @@ -78,7 +78,7 @@ class DoublingChantEffect extends OneShotEffect { FilterCreatureCard filter = new FilterCreatureCard("nothing (no valid card available)"); filter.add(new NamePredicate("creatureName")); TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); - controller.searchLibrary(target, game); + controller.searchLibrary(target, source, game); } } for (Permanent creature : creatures) { @@ -91,7 +91,7 @@ class DoublingChantEffect extends OneShotEffect { filter.add(Predicates.not(Predicates.or(uuidPredicates))); } TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { chosenCards.add(card); diff --git a/Mage.Sets/src/mage/cards/d/DoublingSeason.java b/Mage.Sets/src/mage/cards/d/DoublingSeason.java index fab6ae021d..5620ad2403 100644 --- a/Mage.Sets/src/mage/cards/d/DoublingSeason.java +++ b/Mage.Sets/src/mage/cards/d/DoublingSeason.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; @@ -16,8 +14,9 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DoublingSeason extends CardImpl { @@ -58,7 +57,7 @@ class DoublingSeasonCounterEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() * 2); + event.setAmountForCounters(event.getAmount() * 2, true); return false; } @@ -80,6 +79,7 @@ class DoublingSeasonCounterEffect extends ReplacementEffectImpl { } return permanent != null && permanent.isControlledBy(source.getControllerId()) + && event.getAmount() > 0 && !landPlayed; // example: gemstone mine being played as a land drop } diff --git a/Mage.Sets/src/mage/cards/d/DovinHandOfControl.java b/Mage.Sets/src/mage/cards/d/DovinHandOfControl.java new file mode 100644 index 0000000000..34eaf023bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DovinHandOfControl.java @@ -0,0 +1,94 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.SpellAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.PreventDamageByTargetEffect; +import mage.abilities.effects.common.PreventDamageToTargetEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; +import mage.abilities.mana.ManaAbility; +import mage.game.stack.Spell; + +/** + * @author TheElk801 + */ +public final class DovinHandOfControl extends CardImpl { + + public DovinHandOfControl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{W/U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DOVIN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Artifact, instant, and sorcery spells your opponents cast cost {1} more to cast. + this.addAbility(new SimpleStaticAbility(new DovinHandOfControlEffect())); + + // -1: Until your next turn, prevent all damage that would be dealt to and dealt by target permanent an opponent controls. + Ability ability = new LoyaltyAbility(new PreventDamageToTargetEffect( + Duration.UntilYourNextTurn + ).setText("Until your next turn, prevent all damage that would be dealt to"), -1); + ability.addEffect(new PreventDamageByTargetEffect( + Duration.UntilYourNextTurn + ).setText("and dealt by target permanent an opponent controls")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT)); + this.addAbility(ability); + } + + private DovinHandOfControl(final DovinHandOfControl card) { + super(card); + } + + @Override + public DovinHandOfControl copy() { + return new DovinHandOfControl(this); + } +} + +class DovinHandOfControlEffect extends CostModificationEffectImpl { + + DovinHandOfControlEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); + staticText = "Artifact, instant, and sorcery spells your opponents cast cost {1} more to cast"; + } + + private DovinHandOfControlEffect(DovinHandOfControlEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + SpellAbility spellAbility = (SpellAbility) abilityToModify; + CardUtil.adjustCost(spellAbility, -1); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + Card card = game.getCard(abilityToModify.getSourceId()); + if (!(abilityToModify instanceof SpellAbility)) { + return false; + } + return card != null + && (card.isInstantOrSorcery() + || card.isArtifact()) + && game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId()); + } + + @Override + public DovinHandOfControlEffect copy() { + return new DovinHandOfControlEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DreadhordeInvasion.java b/Mage.Sets/src/mage/cards/d/DreadhordeInvasion.java index 5deecbca9d..98092d6deb 100644 --- a/Mage.Sets/src/mage/cards/d/DreadhordeInvasion.java +++ b/Mage.Sets/src/mage/cards/d/DreadhordeInvasion.java @@ -10,11 +10,12 @@ import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; import mage.filter.predicate.permanent.TokenPredicate; import java.util.UUID; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; /** * @author TheElk801 @@ -27,6 +28,7 @@ public final class DreadhordeInvasion extends CardImpl { static { filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 5)); filter.add(TokenPredicate.instance); + filter.add(new ControllerPredicate(TargetController.YOU)); } public DreadhordeInvasion(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/d/DreadhordeTwins.java b/Mage.Sets/src/mage/cards/d/DreadhordeTwins.java new file mode 100644 index 0000000000..ae9100b342 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DreadhordeTwins.java @@ -0,0 +1,56 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.AmassEffect; +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.FilterPermanent; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DreadhordeTwins extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.ZOMBIE, "Zombie tokens"); + + static { + filter.add(TokenPredicate.instance); + } + + public DreadhordeTwins(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.JACKAL); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Dreadhorde Twins enters the battlefield, amass 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AmassEffect(2))); + + // Zombie tokens you control have trample. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private DreadhordeTwins(final DreadhordeTwins card) { + super(card); + } + + @Override + public DreadhordeTwins copy() { + return new DreadhordeTwins(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/Dreadmalkin.java b/Mage.Sets/src/mage/cards/d/Dreadmalkin.java new file mode 100644 index 0000000000..f31c67f88e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Dreadmalkin.java @@ -0,0 +1,66 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Dreadmalkin extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent("another creature or planeswalker"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.PLANESWALKER), + new CardTypePredicate(CardType.CREATURE) + )); + filter.add(AnotherPredicate.instance); + } + + public Dreadmalkin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.CAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Menace + this.addAbility(new MenaceAbility()); + + // {2}{B}, Sacrifice another creature or planeswalker: Put two +1/+1 counters on Dreadmalkin. + Ability ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), new ManaCostsImpl("{2}{B}") + ); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); + this.addAbility(ability); + } + + private Dreadmalkin(final Dreadmalkin card) { + super(card); + } + + @Override + public Dreadmalkin copy() { + return new Dreadmalkin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DuskmantleOperative.java b/Mage.Sets/src/mage/cards/d/DuskmantleOperative.java new file mode 100644 index 0000000000..4f3d69b3d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuskmantleOperative.java @@ -0,0 +1,51 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleEvasionAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +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.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DuskmantleOperative extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creatures with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public DuskmantleOperative(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Duskmantle Operative can't be blocked by creatures with power 4 or greater. + this.addAbility(new SimpleEvasionAbility(new CantBeBlockedByCreaturesSourceEffect( + filter, Duration.WhileOnBattlefield + ))); + } + + private DuskmantleOperative(final DuskmantleOperative card) { + super(card); + } + + @Override + public DuskmantleOperative copy() { + return new DuskmantleOperative(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarwigSquad.java b/Mage.Sets/src/mage/cards/e/EarwigSquad.java index 41c6b6c225..006d6bf757 100644 --- a/Mage.Sets/src/mage/cards/e/EarwigSquad.java +++ b/Mage.Sets/src/mage/cards/e/EarwigSquad.java @@ -79,7 +79,7 @@ class EarwigSquadEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && opponent != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 3, new FilterCard("cards from opponents library to exile")); - if (player.searchLibrary(target, game, opponent.getId())) { + if (player.searchLibrary(target, source, game, opponent.getId())) { List targets = target.getTargets(); for (UUID targetId : targets) { Card card = opponent.getLibrary().remove(targetId, game); diff --git a/Mage.Sets/src/mage/cards/e/EldritchEvolution.java b/Mage.Sets/src/mage/cards/e/EldritchEvolution.java index c1eecf9e53..4ea99a5191 100644 --- a/Mage.Sets/src/mage/cards/e/EldritchEvolution.java +++ b/Mage.Sets/src/mage/cards/e/EldritchEvolution.java @@ -83,7 +83,7 @@ class EldritchEvolutionEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, newConvertedCost+1)); filter.add(new CardTypePredicate(CardType.CREATURE)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/e/EliteGuardmage.java b/Mage.Sets/src/mage/cards/e/EliteGuardmage.java new file mode 100644 index 0000000000..3ae453a7e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EliteGuardmage.java @@ -0,0 +1,46 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EliteGuardmage extends CardImpl { + + public EliteGuardmage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Elite Guardmage enters the battlefield, you can 3 life and draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(3)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private EliteGuardmage(final EliteGuardmage card) { + super(card); + } + + @Override + public EliteGuardmage copy() { + return new EliteGuardmage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElvishHouseParty.java b/Mage.Sets/src/mage/cards/e/ElvishHouseParty.java new file mode 100644 index 0000000000..d01550b932 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElvishHouseParty.java @@ -0,0 +1,73 @@ + +package mage.cards.e; + +import java.time.LocalTime; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +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.game.Game; + +/** + * + * @author Ketsuban + */ +public final class ElvishHouseParty extends CardImpl { + + public ElvishHouseParty(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{4}{G}{G}"); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Elvish House Party's power and toughness are each equal to the current hour, + // using the twelve-hour system. + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SetPowerToughnessSourceEffect(new CurrentHourCount(), Duration.WhileOnBattlefield))); + } + + public ElvishHouseParty(final ElvishHouseParty card) { + super(card); + } + + @Override + public ElvishHouseParty copy() { + return new ElvishHouseParty(this); + } +} + +class CurrentHourCount implements DynamicValue { + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int hour = LocalTime.now().getHour(); + // convert 24-hour value to 12-hour + if (hour > 12) { + hour -= 12; + } + if (hour == 0) { + hour = 12; + } + return hour; + } + + @Override + public DynamicValue copy() { + return new CurrentHourCount(); + } + + @Override + public String getMessage() { + return "current hour, using the twelve-hour system"; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EndlessHorizons.java b/Mage.Sets/src/mage/cards/e/EndlessHorizons.java index d02030c854..ef0aee658c 100644 --- a/Mage.Sets/src/mage/cards/e/EndlessHorizons.java +++ b/Mage.Sets/src/mage/cards/e/EndlessHorizons.java @@ -74,7 +74,7 @@ class EndlessHorizonsEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player you = game.getPlayer(source.getControllerId()); if (you != null) { - if (you.searchLibrary(target, game)) { + if (you.searchLibrary(target, source, game)) { UUID exileZone = CardUtil.getCardExileZoneId(game, source); if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/e/EnduringIdeal.java b/Mage.Sets/src/mage/cards/e/EnduringIdeal.java index ee3d0919a9..29b2856c17 100644 --- a/Mage.Sets/src/mage/cards/e/EnduringIdeal.java +++ b/Mage.Sets/src/mage/cards/e/EnduringIdeal.java @@ -68,7 +68,7 @@ class EnduringIdealEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - controller.searchLibrary(target, game); + controller.searchLibrary(target, source, game); Card targetCard = game.getCard(target.getFirstTarget()); if (targetCard == null) { applied = false; diff --git a/Mage.Sets/src/mage/cards/e/EnforcerGriffin.java b/Mage.Sets/src/mage/cards/e/EnforcerGriffin.java new file mode 100644 index 0000000000..ab651488de --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EnforcerGriffin.java @@ -0,0 +1,36 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EnforcerGriffin extends CardImpl { + + public EnforcerGriffin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.GRIFFIN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + } + + private EnforcerGriffin(final EnforcerGriffin card) { + super(card); + } + + @Override + public EnforcerGriffin copy() { + return new EnforcerGriffin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EnterTheGodEternals.java b/Mage.Sets/src/mage/cards/e/EnterTheGodEternals.java new file mode 100644 index 0000000000..fe6bdebf9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EnterTheGodEternals.java @@ -0,0 +1,82 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.AmassEffect; +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.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EnterTheGodEternals extends CardImpl { + + public EnterTheGodEternals(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}{U}{B}"); + + // Enter the God-Eternals deals 4 damage to target creature and you gain life equal to the damage dealt this way. Target player puts the top four cards of their library into their graveyard. Amass 4. + this.getSpellAbility().addEffect(new EnterTheGodEternalsEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private EnterTheGodEternals(final EnterTheGodEternals card) { + super(card); + } + + @Override + public EnterTheGodEternals copy() { + return new EnterTheGodEternals(this); + } +} + +class EnterTheGodEternalsEffect extends OneShotEffect { + + EnterTheGodEternalsEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 4 damage to target creature and you gain life equal to the damage dealt this way. " + + "Target player puts the top four cards of their library into their graveyard. Amass 4."; + } + + private EnterTheGodEternalsEffect(final EnterTheGodEternalsEffect effect) { + super(effect); + } + + @Override + public EnterTheGodEternalsEffect copy() { + return new EnterTheGodEternalsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + for (Target target : source.getTargets()) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + controller.gainLife(permanent.damage(4, source.getSourceId(), game), game, source); + continue; + } + Player player = game.getPlayer(targetId); + if (player != null) { + player.moveCards(player.getLibrary().getTopCards(game, 4), Zone.GRAVEYARD, source, game); + } + } + } + return new AmassEffect(4).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/e/Entomb.java b/Mage.Sets/src/mage/cards/e/Entomb.java index acf119d0c5..039d09c2dc 100644 --- a/Mage.Sets/src/mage/cards/e/Entomb.java +++ b/Mage.Sets/src/mage/cards/e/Entomb.java @@ -60,7 +60,7 @@ class SearchLibraryPutInGraveyard extends SearchEffect { if (controller == null) { return false; } - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(game.getCard(target.getFirstTarget()), Zone.GRAVEYARD, source, game); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java b/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java index b9715cbc12..90b8131918 100644 --- a/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java +++ b/Mage.Sets/src/mage/cards/e/EntrailsFeaster.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -9,11 +7,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.common.FilterCreatureCard; import mage.game.Game; @@ -21,14 +15,15 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCardInGraveyard; +import java.util.UUID; + /** - * * @author L_J */ public final class EntrailsFeaster extends CardImpl { public EntrailsFeaster(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); this.subtype.add(SubType.ZOMBIE); this.subtype.add(SubType.CAT); this.power = new MageInt(1); @@ -81,7 +76,7 @@ class EntrailsFeasterEffect extends OneShotEffect { if (cardChosen != null) { controller.moveCardsToExile(cardChosen, source, game, true, null, ""); if (sourceObject != null) { - sourceObject.getCounters(game).addCounter(CounterType.P1P1.createInstance()); + sourceObject.addCounters(CounterType.P1P1.createInstance(), source, game); game.informPlayers(controller.getLogName() + " puts a +1/+1 counter on " + sourceObject.getLogName()); } } diff --git a/Mage.Sets/src/mage/cards/e/EssenceOfTheWild.java b/Mage.Sets/src/mage/cards/e/EssenceOfTheWild.java index 2d1a030d4d..eb64cf137c 100644 --- a/Mage.Sets/src/mage/cards/e/EssenceOfTheWild.java +++ b/Mage.Sets/src/mage/cards/e/EssenceOfTheWild.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -9,25 +7,22 @@ import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.CopyEffect; 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.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author BetaSteward */ public final class EssenceOfTheWild extends CardImpl { public EssenceOfTheWild(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}{G}"); this.subtype.add(SubType.AVATAR); this.power = new MageInt(6); @@ -73,11 +68,7 @@ class EssenceOfTheWildEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Permanent sourceObject = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (sourceObject != null) { - Permanent permanentReset = sourceObject.copy(); - permanentReset.getCounters(game).clear(); - permanentReset.getPower().resetToBaseValue(); - permanentReset.getToughness().resetToBaseValue(); - game.addEffect(new CopyEffect(Duration.Custom, permanentReset, event.getTargetId()), source); + game.addEffect(new CopyEffect(Duration.Custom, sourceObject, event.getTargetId()), source); } return false; } diff --git a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java index 5c5ff2373d..8e1f289874 100644 --- a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java +++ b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java @@ -97,6 +97,7 @@ class EtaliPrimalStormEffect extends OneShotEffect { } } } + // cast the possible cards without paying the mana Cards cardsToCast = new CardsImpl(); cardsToCast.addAll(currentExiledCards); @@ -105,17 +106,21 @@ class EtaliPrimalStormEffect extends OneShotEffect { if (!controller.chooseUse(Outcome.PlayForFree, "Cast a" + (alreadyCast ? "nother" : "") + " card exiled with " + sourceObject.getLogName() + " without paying its mana cost?", source, game)) { break; } + TargetCard targetCard = new TargetCard(1, Zone.EXILED, new FilterCard("nonland card to cast for free")); - if (controller.choose(Outcome.PlayForFree, cardsToCast, targetCard, game)) { - alreadyCast = true; - Card card = game.getCard(targetCard.getFirstTarget()); - if (card != null) { - if (controller.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game))) { - cardsToCast.remove(card); - } else { + if (!controller.choose(Outcome.PlayForFree, cardsToCast, targetCard, game)) { + break; + } + + alreadyCast = true; + Card card = game.getCard(targetCard.getFirstTarget()); + if (card != null) { + if (!controller.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game))) { + if (!game.isSimulation()) { game.informPlayer(controller, "You're not able to cast " + card.getIdName() + " or you canceled the casting."); } } + cardsToCast.remove(card); } } return true; diff --git a/Mage.Sets/src/mage/cards/e/EternalDominion.java b/Mage.Sets/src/mage/cards/e/EternalDominion.java index 74c32ebd33..ebaa4377f1 100644 --- a/Mage.Sets/src/mage/cards/e/EternalDominion.java +++ b/Mage.Sets/src/mage/cards/e/EternalDominion.java @@ -77,7 +77,7 @@ class EternalDominionEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (opponent != null && controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(FILTER); - controller.searchLibrary(target, game, opponent.getId()); + controller.searchLibrary(target, source, game, opponent.getId()); Card targetCard = game.getCard(target.getFirstTarget()); if (targetCard != null) { applied = controller.moveCards(targetCard, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/e/EternalSkylord.java b/Mage.Sets/src/mage/cards/e/EternalSkylord.java new file mode 100644 index 0000000000..a6f9bf7626 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EternalSkylord.java @@ -0,0 +1,56 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.AmassEffect; +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.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EternalSkylord extends CardImpl { + + private static final FilterPermanent filter + = new FilterPermanent(SubType.ZOMBIE, "Zombie tokens"); + + static { + filter.add(TokenPredicate.instance); + } + + public EternalSkylord(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Eternal Skylord enters the batttlefield, amass 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AmassEffect(2))); + + // Zombie tokens you control have flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private EternalSkylord(final EternalSkylord card) { + super(card); + } + + @Override + public EternalSkylord copy() { + return new EternalSkylord(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EverythingamajigB.java b/Mage.Sets/src/mage/cards/e/EverythingamajigB.java new file mode 100644 index 0000000000..4c87ca972a --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EverythingamajigB.java @@ -0,0 +1,74 @@ + +package mage.cards.e; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.HellbentCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.filter.predicate.other.ExpansionSetPredicate; + +/** + * + * @author Ketsuban + */ +public final class EverythingamajigB extends CardImpl { + + private static final FilterPermanentCard filter = new FilterPermanentCard("a silver-bordered permanent card"); + + static { + filter.add(Predicates.and( + Predicates.not(new SupertypePredicate(SuperType.BASIC)), // all Un-set basic lands are black bordered cards, and thus illegal choices + Predicates.not(new NamePredicate("Steamflogger Boss")), // printed in Unstable with a black border + Predicates.or(new ExpansionSetPredicate("UGL"), new ExpansionSetPredicate("UNH"), new ExpansionSetPredicate("UST")) + )); + } + + public EverythingamajigB(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + + // Fool's Tome + // 2, T: Draw a card. Activate this ability only if you have no cards in hand. + Ability ability1 = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new GenericManaCost(2), HellbentCondition.instance); + ability1.addCost(new TapSourceCost()); + this.addAbility(ability1); + + // Tower of Eons + // 8, T: You gain 10 life. + Ability ability2 = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(10), new GenericManaCost(8)); + ability2.addCost(new TapSourceCost()); + this.addAbility(ability2); + + // Spatula of the Ages + // 4, T, Sacrifice Everythingamajig: You may put a silver-bordered permanent card from your hand onto the battlefield. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PutCardFromHandOntoBattlefieldEffect(filter), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public EverythingamajigB(final EverythingamajigB card) { + super(card); + } + + @Override + public EverythingamajigB copy() { + return new EverythingamajigB(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EverythingamajigC.java b/Mage.Sets/src/mage/cards/e/EverythingamajigC.java new file mode 100644 index 0000000000..9868a33f90 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EverythingamajigC.java @@ -0,0 +1,179 @@ + +package mage.cards.e; + +import java.util.UUID; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.VariableManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +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.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author Ketsuban + */ +public final class EverythingamajigC extends CardImpl { + + public EverythingamajigC(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + + // Mana Screw + // 1: Flip a coin. If you win the flip, add CC to your mana pool. Activate this ability only any time you could cast an instant. + this.addAbility(new ManaScrewAbility()); + + // Disrupting Scepter + // 3, T: Target player discards a card. Activate this ability only during your turn. + Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, new DiscardTargetEffect(1), new GenericManaCost(3), MyTurnCondition.instance); + ability.addTarget(new TargetPlayer()); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // Chimeric Staff + // X: Everythingamajig becomes an X/X Construct artifact creature until end of turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ChimericStaffEffect(), new VariableManaCost())); + } + + public EverythingamajigC(final EverythingamajigC card) { + super(card); + } + + @Override + public EverythingamajigC copy() { + return new EverythingamajigC(this); + } +} + +class ManaScrewAbility extends ActivatedManaAbilityImpl { + + public ManaScrewAbility() { + super(Zone.BATTLEFIELD, new ManaScrewEffect(), new GenericManaCost(1)); + this.netMana.add(new Mana(0, 0, 0, 0, 0, 2, 0, 0)); + } + + public ManaScrewAbility(final ManaScrewAbility ability) { + super(ability); + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + Player player = game.getPlayer(playerId); + if (player != null && !player.isInPayManaMode()) { + return super.canActivate(playerId, game); + } + return ActivationStatus.getFalse(); + } + + @Override + public ManaScrewAbility copy() { + return new ManaScrewAbility(this); + } + + @Override + public String getRule() { + return super.getRule() + " Activate this ability only any time you could cast an instant."; + } +} + +class ManaScrewEffect extends BasicManaEffect { + + public ManaScrewEffect() { + super(Mana.ColorlessMana(2)); + this.staticText = "Flip a coin. If you win the flip, add {C}{C}"; + } + + public ManaScrewEffect(final ManaScrewEffect effect) { + super(effect); + this.manaTemplate = effect.manaTemplate.copy(); + } + + @Override + public ManaScrewEffect copy() { + return new ManaScrewEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null && player.flipCoin(source, game, true)) { + player.getManaPool().addMana(getMana(game, source), game, source); + } + return true; + } +} + +class ChimericStaffEffect extends ContinuousEffectImpl { + + public ChimericStaffEffect() { + super(Duration.EndOfTurn, Outcome.BecomeCreature); + setText(); + } + + public ChimericStaffEffect(final ChimericStaffEffect effect) { + super(effect); + } + + @Override + public ChimericStaffEffect copy() { + return new ChimericStaffEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + switch (layer) { + case TypeChangingEffects_4: + if (sublayer == SubLayer.NA) { + permanent.addCardType(CardType.CREATURE); + permanent.getSubtype(game).add(SubType.CONSTRUCT); + } + break; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + int xValue = source.getManaCostsToPay().getX(); + if (xValue != 0) { + permanent.getPower().setValue(xValue); + permanent.getToughness().setValue(xValue); + } + } + } + return true; + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + private void setText() { + staticText = duration.toString() + " {this} becomes an X/X Construct artifact creature"; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.PTChangingEffects_7 || layer == Layer.TypeChangingEffects_4; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EverythingamajigE.java b/Mage.Sets/src/mage/cards/e/EverythingamajigE.java new file mode 100644 index 0000000000..bad33f9ba5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EverythingamajigE.java @@ -0,0 +1,148 @@ + +package mage.cards.e; + +import java.util.UUID; + +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +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.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author Ketsuban + */ +public final class EverythingamajigE extends CardImpl { + + public EverythingamajigE(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + + // Zuran Orb + // Sacrifice a land: You gain 2 life. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(2), new SacrificeTargetCost(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_LAND_SHORT_TEXT)))); + + // Ashnod's Altar + // Sacrifice a creature: Add CC to your mana pool. + SacrificeTargetCost cost = new SacrificeTargetCost(new TargetControlledCreaturePermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)); + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.ColorlessMana(2), cost)); + + // Urza's Hot Tub + // 2, Discard a card: Search your library for a card that shares a complete word in its name with the name of the discarded card, reveal it, put it into your hand, then shuffle your library. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new UrzasHotTubEffect(), new GenericManaCost(2)); + ability.addCost(new DiscardTargetCost(new TargetCardInHand())); + this.addAbility(ability); + } + + public EverythingamajigE(final EverythingamajigE card) { + super(card); + } + + @Override + public EverythingamajigE copy() { + return new EverythingamajigE(this); + } +} + +class UrzasHotTubEffect extends OneShotEffect { + + public UrzasHotTubEffect() { + super(Outcome.ReturnToHand); + this.staticText = "Search your library for a card that shares a complete word in its name with the discarded card, reveal it, put it into your hand, then shuffle your library"; + } + + public UrzasHotTubEffect(final UrzasHotTubEffect effect) { + super(effect); + } + + @Override + public UrzasHotTubEffect copy() { + return new UrzasHotTubEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Cost cost : source.getCosts()) { + if (cost instanceof DiscardTargetCost) { + DiscardTargetCost discardCost = (DiscardTargetCost) cost; + Card discardedCard = discardCost.getCards().get(0); + if (discardedCard != null) { + FilterCard filter = new FilterCard(); + filter.add(new UrzasHotTubPredicate(discardedCard.getName())); + return new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true, true).apply(game, source); + } + } + } + return false; + } +} + +class UrzasHotTubPredicate implements Predicate { + + private final String referenceName; + + public UrzasHotTubPredicate(String referenceName) { + this.referenceName = referenceName; + } + + @Override + public boolean apply(MageObject input, Game game) { + String name = input.getName(); + if (input instanceof SplitCard) { + return sharesWordWithName(((SplitCard)input).getLeftHalfCard().getName()) || sharesWordWithName(((SplitCard)input).getRightHalfCard().getName()); + } else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED){ + SplitCard card = (SplitCard) ((Spell)input).getCard(); + return sharesWordWithName(card.getLeftHalfCard().getName()) || sharesWordWithName(card.getRightHalfCard().getName()); + } else { + if (name.contains(" // ")) { + String leftName = name.substring(0, name.indexOf(" // ")); + String rightName = name.substring(name.indexOf(" // ") + 4, name.length()); + return sharesWordWithName(leftName) || sharesWordWithName(rightName); + } else { + return sharesWordWithName(name); + } + } + } + + private boolean sharesWordWithName(String str) { + if (referenceName == null || referenceName.equals("")) { + return false; + } + String[] arr = referenceName.split("\\s+"); + for (int i = 0; i < arr.length; i++) { + if (str.contains(arr[i].replaceAll(",", ""))) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EvolutionSage.java b/Mage.Sets/src/mage/cards/e/EvolutionSage.java new file mode 100644 index 0000000000..9bda060621 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvolutionSage.java @@ -0,0 +1,47 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EvolutionSage extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledLandPermanent("a land"); + + public EvolutionSage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever a land enters the battlefield under your control, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, new ProliferateEffect(), filter, + false, null, true + )); + } + + private EvolutionSage(final EvolutionSage card) { + super(card); + } + + @Override + public EvolutionSage copy() { + return new EvolutionSage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExpelFromOrazca.java b/Mage.Sets/src/mage/cards/e/ExpelFromOrazca.java index 8feec69918..9c979fb627 100644 --- a/Mage.Sets/src/mage/cards/e/ExpelFromOrazca.java +++ b/Mage.Sets/src/mage/cards/e/ExpelFromOrazca.java @@ -1,10 +1,10 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.AscendEffect; +import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.PermanentsYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -16,8 +16,9 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetNonlandPermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ExpelFromOrazca extends CardImpl { @@ -25,8 +26,10 @@ public final class ExpelFromOrazca extends CardImpl { public ExpelFromOrazca(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); - // Ascend + // Ascend (If you control ten or more permanents, you get the city’s blessing for the rest of the game.) this.getSpellAbility().addEffect(new AscendEffect()); + this.getSpellAbility().addHint(CitysBlessingHint.instance); + this.getSpellAbility().addHint(PermanentsYouControlHint.instance); // Return target nonland permanent to its owner's hand. If you have the city's blessing, you may put that permanent on top of its owner's library instead. this.getSpellAbility().addEffect(new ExpelFromOrazcaEffect()); diff --git a/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java b/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java index 50dcb6b5a4..e4d6d07d25 100644 --- a/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java +++ b/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java @@ -1,30 +1,25 @@ package mage.cards.e; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.DestroySourceEffect; +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.Duration; -import mage.constants.Layer; import mage.constants.Outcome; -import mage.constants.SubLayer; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.players.Player; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class ExperimentalFrenzy extends CardImpl { @@ -33,27 +28,19 @@ public final class ExperimentalFrenzy extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); // You may look at the top card of your library any time. - this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, new ExperimentalFrenzyTopCardEffect() - )); + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may play the top card of your library. - this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, new PlayTheTopCardEffect() - )); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect())); // You can't play cards from your hand. - this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, new ExperimentalFrenzyRestrictionEffect() - )); + this.addAbility(new SimpleStaticAbility(new ExperimentalFrenzyRestrictionEffect())); // {3}{R}: Destroy Experimental Frenzy. - this.addAbility(new SimpleActivatedAbility( - new DestroySourceEffect(), new ManaCostsImpl("{3}{R}") - )); + this.addAbility(new SimpleActivatedAbility(new DestroySourceEffect(), new ManaCostsImpl("{3}{R}"))); } - public ExperimentalFrenzy(final ExperimentalFrenzy card) { + private ExperimentalFrenzy(final ExperimentalFrenzy card) { super(card); } @@ -63,49 +50,14 @@ public final class ExperimentalFrenzy extends CardImpl { } } -class ExperimentalFrenzyTopCardEffect extends ContinuousEffectImpl { - - public ExperimentalFrenzyTopCardEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - staticText = "You may look at the top card of your library any time."; - } - - public ExperimentalFrenzyTopCardEffect(final ExperimentalFrenzyTopCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return true; - } - Card topCard = controller.getLibrary().getFromTop(game); - if (topCard == null) { - return true; - } - MageObject obj = source.getSourceObject(game); - if (obj == null) { - return true; - } - controller.lookAtCards("Top card of " + obj.getIdName() + " controller's library", topCard, game); - return true; - } - - @Override - public ExperimentalFrenzyTopCardEffect copy() { - return new ExperimentalFrenzyTopCardEffect(this); - } -} - class ExperimentalFrenzyRestrictionEffect extends ContinuousRuleModifyingEffectImpl { - public ExperimentalFrenzyRestrictionEffect() { + ExperimentalFrenzyRestrictionEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "You can't play cards from your hand"; } - public ExperimentalFrenzyRestrictionEffect(final ExperimentalFrenzyRestrictionEffect effect) { + private ExperimentalFrenzyRestrictionEffect(final ExperimentalFrenzyRestrictionEffect effect) { super(effect); } diff --git a/Mage.Sets/src/mage/cards/e/Expropriate.java b/Mage.Sets/src/mage/cards/e/Expropriate.java index 81761c64af..318fa8342f 100644 --- a/Mage.Sets/src/mage/cards/e/Expropriate.java +++ b/Mage.Sets/src/mage/cards/e/Expropriate.java @@ -1,8 +1,5 @@ package mage.cards.e; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; @@ -22,6 +19,10 @@ import mage.target.Target; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** * @author JRHerlehy */ @@ -30,7 +31,8 @@ public final class Expropriate extends CardImpl { public Expropriate(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{7}{U}{U}"); - // Council's dilemma — Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate + // Council's dilemma — Starting with you, each player votes for time or money. For each time vote, + // take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate this.getSpellAbility().addEffect(new ExpropriateDilemmaEffect()); this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); } diff --git a/Mage.Sets/src/mage/cards/e/Extirpate.java b/Mage.Sets/src/mage/cards/e/Extirpate.java index 782e418adb..1c74ffd1df 100644 --- a/Mage.Sets/src/mage/cards/e/Extirpate.java +++ b/Mage.Sets/src/mage/cards/e/Extirpate.java @@ -116,7 +116,7 @@ class ExtirpateEffect extends OneShotEffect { // search cards in Library filterNamedCard.setMessage("card named " + chosenCard.getName() + " in the library of " + owner.getName()); TargetCardInLibrary targetCardInLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCard); - if (controller.searchLibrary(targetCardInLibrary, game, owner.getId())) { + if (controller.searchLibrary(targetCardInLibrary, source, game, owner.getId())) { List targets = targetCardInLibrary.getTargets(); for (UUID targetId : targets) { Card targetCard = owner.getLibrary().getCard(targetId, game); diff --git a/Mage.Sets/src/mage/cards/e/Extract.java b/Mage.Sets/src/mage/cards/e/Extract.java index 172eddb25e..cb11605321 100644 --- a/Mage.Sets/src/mage/cards/e/Extract.java +++ b/Mage.Sets/src/mage/cards/e/Extract.java @@ -66,7 +66,7 @@ class ExtractEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && targetPlayer != null) { TargetCardInLibrary target = new TargetCardInLibrary(1, 1, filter); - if (player.searchLibrary(target, game, targetPlayer.getId())) { + if (player.searchLibrary(target, source, game, targetPlayer.getId())) { Card card = targetPlayer.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { player.moveCardToExileWithInfo(card, null, null, source.getSourceId(), game, Zone.LIBRARY, true); diff --git a/Mage.Sets/src/mage/cards/f/FblthpTheLost.java b/Mage.Sets/src/mage/cards/f/FblthpTheLost.java new file mode 100644 index 0000000000..3de4f8abcd --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FblthpTheLost.java @@ -0,0 +1,175 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObject; +import mage.MageObjectReference; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ShuffleIntoLibrarySourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FblthpTheLost extends CardImpl { + + public FblthpTheLost(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HOMUNCULUS); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Fblthp, the Lost enters the battlefield, draw a card. If it entered from your library or was cast from your library, draw two cards instead. + this.addAbility(new FblthpTheLostTriggeredAbility()); + + // When Fblthp becomes the target of a spell, shuffle Fblthp into its owner's library. + this.addAbility(new FblthpTheLostTargetedTriggeredAbility()); + } + + private FblthpTheLost(final FblthpTheLost card) { + super(card); + } + + @Override + public FblthpTheLost copy() { + return new FblthpTheLost(this); + } +} + +class FblthpTheLostTriggeredAbility extends EntersBattlefieldTriggeredAbility { + FblthpTheLostTriggeredAbility() { + super(new DrawCardSourceControllerEffect(1)); + this.addWatcher(new FblthpTheLostWatcher()); + } + + private FblthpTheLostTriggeredAbility(final FblthpTheLostTriggeredAbility ability) { + super(ability); + } + + public FblthpTheLostTriggeredAbility copy() { + return new FblthpTheLostTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!super.checkTrigger(event, game)) { + return false; + } + EntersTheBattlefieldEvent entersEvent = (EntersTheBattlefieldEvent) event; + if (entersEvent.getFromZone() == Zone.LIBRARY) { + this.getEffects().clear(); + this.addEffect(new DrawCardSourceControllerEffect(2)); + return true; + } + FblthpTheLostWatcher watcher = game.getState().getWatcher(FblthpTheLostWatcher.class); + int zcc = entersEvent.getTarget().getZoneChangeCounter(game) - 1; + MageObjectReference mor = new MageObjectReference(entersEvent.getTargetId(), zcc, game); + if (watcher != null && watcher.spellWasCastFromLibrary(mor)) { + this.getEffects().clear(); + this.addEffect(new DrawCardSourceControllerEffect(2)); + return true; + } + this.getEffects().clear(); + this.addEffect(new DrawCardSourceControllerEffect(1)); + return true; + } + + @Override + public String getRule() { + return "When {this} enters the battlefield, draw a card. " + + "If it entered from your library or was cast from your library, draw two cards instead."; + } +} + +class FblthpTheLostWatcher extends Watcher { + + private final Set spellsCastFromLibrary = new HashSet(); + + FblthpTheLostWatcher() { + super(WatcherScope.GAME); + } + + private FblthpTheLostWatcher(final FblthpTheLostWatcher watcher) { + super(watcher); + spellsCastFromLibrary.addAll(watcher.spellsCastFromLibrary); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone() == Zone.LIBRARY) { + Spell spell = (Spell) game.getObject(event.getTargetId()); + if (spell != null) { + spellsCastFromLibrary.add(new MageObjectReference(spell, game)); + } + + } + } + + boolean spellWasCastFromLibrary(MageObjectReference mor) { + return spellsCastFromLibrary.contains(mor); + + } + + @Override + public void reset() { + super.reset(); + spellsCastFromLibrary.clear(); + } + + @Override + public FblthpTheLostWatcher copy() { + return new FblthpTheLostWatcher(this); + } +} + +class FblthpTheLostTargetedTriggeredAbility extends TriggeredAbilityImpl { + + FblthpTheLostTargetedTriggeredAbility() { + super(Zone.BATTLEFIELD, new ShuffleIntoLibrarySourceEffect(), false); + } + + private FblthpTheLostTargetedTriggeredAbility(final FblthpTheLostTargetedTriggeredAbility ability) { + super(ability); + } + + @Override + public FblthpTheLostTargetedTriggeredAbility copy() { + return new FblthpTheLostTargetedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGETED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + MageObject eventSourceObject = game.getObject(event.getSourceId()); + if (event.getTargetId().equals(this.getSourceId()) && eventSourceObject instanceof Spell) { + getEffects().get(0).setTargetPointer(new FixedTarget(event.getPlayerId())); + return true; + } + return false; + } + + @Override + public String getRule() { + return "When {this} becomes the target of a spell, shuffle {this} into its owner's library."; + } + +} diff --git a/Mage.Sets/src/mage/cards/f/FeatherTheRedeemed.java b/Mage.Sets/src/mage/cards/f/FeatherTheRedeemed.java new file mode 100644 index 0000000000..ebb28bb5b9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FeatherTheRedeemed.java @@ -0,0 +1,182 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.Target; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FeatherTheRedeemed extends CardImpl { + + public FeatherTheRedeemed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast an instant or sorcery spell that targets a creature you control, exile that card instead of putting it into your graveyard as it resolves. If you do, return it to your hand at the beginning of the next end step. + this.addAbility(new FeatherTheRedeemedTriggeredAbility()); + } + + private FeatherTheRedeemed(final FeatherTheRedeemed card) { + super(card); + } + + @Override + public FeatherTheRedeemed copy() { + return new FeatherTheRedeemed(this); + } +} + +class FeatherTheRedeemedTriggeredAbility extends TriggeredAbilityImpl { + + FeatherTheRedeemedTriggeredAbility() { + super(Zone.BATTLEFIELD, null, false); + } + + private FeatherTheRedeemedTriggeredAbility(final FeatherTheRedeemedTriggeredAbility ability) { + super(ability); + } + + @Override + public FeatherTheRedeemedTriggeredAbility copy() { + return new FeatherTheRedeemedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getPlayerId().equals(this.getControllerId())) { + return false; + } + Spell spell = game.getStack().getSpell(event.getTargetId()); + return checkSpell(spell, game); + } + + private boolean checkSpell(Spell spell, Game game) { + if (spell == null) { + return false; + } + SpellAbility sa = spell.getSpellAbility(); + for (UUID modeId : sa.getModes().getSelectedModes()) { + Mode mode = sa.getModes().get(modeId); + for (Target target : mode.getTargets()) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.isCreature() + && permanent.isControlledBy(getControllerId())) { + this.getEffects().clear(); + this.addEffect(new FeatherTheRedeemedEffect(new MageObjectReference(spell, game))); + return true; + } + } + } + for (Effect effect : mode.getEffects()) { + for (UUID targetId : effect.getTargetPointer().getTargets(game, sa)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.isCreature() + && permanent.isControlledBy(getControllerId())) { + this.getEffects().clear(); + this.addEffect(new FeatherTheRedeemedEffect(new MageObjectReference(spell, game))); + return true; + } + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you cast an instant or sorcery spell that targets a creature you control, " + + "exile that card instead of putting it into your graveyard as it resolves. " + + "If you do, return it to your hand at the beginning of the next end step."; + } +} + +class FeatherTheRedeemedEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + FeatherTheRedeemedEffect(MageObjectReference mor) { + super(Duration.WhileOnStack, Outcome.Benefit); + this.mor = mor; + } + + private FeatherTheRedeemedEffect(final FeatherTheRedeemedEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Spell sourceSpell = game.getStack().getSpell(event.getTargetId()); + if (sourceSpell == null || sourceSpell.isCopy()) { + return false; + } + Player player = game.getPlayer(sourceSpell.getOwnerId()); + if (player == null) { + return false; + } + Effect effect = new ReturnToHandTargetEffect().setText("return " + sourceSpell.getName() + " to its owner's hand"); + player.moveCards(sourceSpell, Zone.EXILED, source, game); + effect.setTargetPointer(new FixedTarget(event.getTargetId(), game)); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + return true; + } + + @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); + if (zEvent.getFromZone() == Zone.STACK + && zEvent.getToZone() == Zone.GRAVEYARD + && event.getSourceId() != null) { + if (event.getSourceId().equals(event.getTargetId()) && mor.getZoneChangeCounter() == game.getState().getZoneChangeCounter(event.getSourceId())) { + Spell spell = game.getStack().getSpell(mor.getSourceId()); + return spell != null && spell.isInstantOrSorcery(); + } + } + return false; + } + + @Override + public FeatherTheRedeemedEffect copy() { + return new FeatherTheRedeemedEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FieldOfRuin.java b/Mage.Sets/src/mage/cards/f/FieldOfRuin.java index 3b559ff471..8070b83b55 100644 --- a/Mage.Sets/src/mage/cards/f/FieldOfRuin.java +++ b/Mage.Sets/src/mage/cards/f/FieldOfRuin.java @@ -90,7 +90,7 @@ class FieldOfRuinEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/f/FinalParting.java b/Mage.Sets/src/mage/cards/f/FinalParting.java index 132b8f9f25..2705156ea5 100644 --- a/Mage.Sets/src/mage/cards/f/FinalParting.java +++ b/Mage.Sets/src/mage/cards/f/FinalParting.java @@ -1,7 +1,6 @@ package mage.cards.f; -import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -66,7 +65,7 @@ class FinalPartingEffect extends OneShotEffect { if (controller != null) { // Unlike Jarad's Orders, which this mostly copies, you can't fail to find TargetCardInLibrary target = new TargetCardInLibrary(2, 2, new FilterCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards searched = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfDevastation.java b/Mage.Sets/src/mage/cards/f/FinaleOfDevastation.java new file mode 100644 index 0000000000..7afc58140a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinaleOfDevastation.java @@ -0,0 +1,80 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; + +import mage.abilities.Ability; +import mage.constants.Outcome; +import mage.game.Game; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.constants.CardType; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.abilities.effects.common.search.SearchLibraryGraveyardWithLessCMCPutIntoPlay; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.constants.Duration; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; + + +/** + * + * @author antoni-g + */ +public final class FinaleOfDevastation extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature"); + + static { + filter.add(new CardTypePredicate(CardType.CREATURE)); + } + + public FinaleOfDevastation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{G}{G}"); + // Search your library and/or graveyard for a creature card with converted mana cost X or less and put it onto the battlefield. If you search your library this way, shuffle it. + this.getSpellAbility().addEffect(new SearchLibraryGraveyardWithLessCMCPutIntoPlay(filter)); + // If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn. + this.getSpellAbility().addEffect(new FinaleOfDevastationEffect()); + } + + private FinaleOfDevastation(final FinaleOfDevastation card) { + super(card); + } + + @Override + public FinaleOfDevastation copy() { + return new FinaleOfDevastation(this); + } +} + +class FinaleOfDevastationEffect extends OneShotEffect { + + FinaleOfDevastationEffect() { + super(Outcome.Benefit); + staticText = "If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn."; + } + + private FinaleOfDevastationEffect(final FinaleOfDevastationEffect effect) { + super(effect); + } + + @Override + public FinaleOfDevastationEffect copy() { + return new FinaleOfDevastationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = source.getManaCostsToPay().getX(); + if (xValue >= 10) { + ContinuousEffect effect1 = new BoostControlledEffect(xValue, xValue, Duration.EndOfTurn); + game.addEffect(effect1, source); + ContinuousEffect effect2 = new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.EndOfTurn, new FilterCreaturePermanent()); + game.addEffect(effect2, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfEternity.java b/Mage.Sets/src/mage/cards/f/FinaleOfEternity.java new file mode 100644 index 0000000000..b753855442 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinaleOfEternity.java @@ -0,0 +1,92 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ToughnessPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetadjustment.TargetAdjuster; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FinaleOfEternity extends CardImpl { + + public FinaleOfEternity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{B}{B}"); + + // Destroy up to three target creatures with toughness X or less. If X is 10 or more, return all creature cards from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new FinaleOfEternityEffect()); + this.getSpellAbility().setTargetAdjuster(FinaleOfEternityAdjuster.instance); + } + + private FinaleOfEternity(final FinaleOfEternity card) { + super(card); + } + + @Override + public FinaleOfEternity copy() { + return new FinaleOfEternity(this); + } +} + +enum FinaleOfEternityAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + int xValue = ability.getManaCostsToPay().getX(); + FilterPermanent filter = new FilterCreaturePermanent("creatures with toughness " + xValue + " or less"); + filter.add(new ToughnessPredicate(ComparisonType.FEWER_THAN, xValue + 1)); + ability.getTargets().clear(); + ability.addTarget(new TargetPermanent(0, 3, filter, false)); + } +} + +class FinaleOfEternityEffect extends OneShotEffect { + + FinaleOfEternityEffect() { + super(Outcome.Benefit); + staticText = "Destroy up to three target creatures with toughness X or less. " + + "If X is 10 or more, return all creature cards from your graveyard to the battlefield."; + } + + private FinaleOfEternityEffect(final FinaleOfEternityEffect effect) { + super(effect); + } + + @Override + public FinaleOfEternityEffect copy() { + return new FinaleOfEternityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + new DestroyTargetEffect(false, true).apply(game, source); + if (source.getManaCostsToPay().getX() < 10) { + return true; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + return player.moveCards( + player.getGraveyard().getCards( + StaticFilters.FILTER_CARD_CREATURE, game + ), Zone.BATTLEFIELD, source, game + ); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfGlory.java b/Mage.Sets/src/mage/cards/f/FinaleOfGlory.java new file mode 100644 index 0000000000..9f1017131f --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinaleOfGlory.java @@ -0,0 +1,67 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.AngelVigilanceToken; +import mage.game.permanent.token.SoldierVigilanceToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FinaleOfGlory extends CardImpl { + + public FinaleOfGlory(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{W}{W}"); + + // Create X 2/2 white Soldier creature tokens with vigilance. If X is 10 or more, also create X 4/4 white Angel creature tokens with flying and vigilance. + this.getSpellAbility().addEffect(new FinaleOfGloryEffect()); + } + + private FinaleOfGlory(final FinaleOfGlory card) { + super(card); + } + + @Override + public FinaleOfGlory copy() { + return new FinaleOfGlory(this); + } +} + +class FinaleOfGloryEffect extends OneShotEffect { + + FinaleOfGloryEffect() { + super(Outcome.Benefit); + staticText = "Create X 2/2 white Soldier creature tokens with vigilance. " + + "If X is 10 or more, also create X 4/4 white Angel creature tokens with flying and vigilance."; + } + + private FinaleOfGloryEffect(final FinaleOfGloryEffect effect) { + super(effect); + } + + @Override + public FinaleOfGloryEffect copy() { + return new FinaleOfGloryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = source.getManaCostsToPay().getX(); + if (xValue == 0) { + return false; + } + new CreateTokenEffect(new SoldierVigilanceToken(), xValue).apply(game, source); + if (xValue >= 10) { + new CreateTokenEffect(new AngelVigilanceToken(), xValue).apply(game, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java new file mode 100644 index 0000000000..339ef3a9d3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java @@ -0,0 +1,198 @@ +package mage.cards.f; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetpointer.FixedTarget; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author JayDi85 + */ +public final class FinaleOfPromise extends CardImpl { + + static final FilterCard filterInstant = new FilterCard("instant card from your graveyard"); + static final FilterCard filterSorcery = new FilterCard("sorcery card from your graveyard"); + + static { + filterInstant.add(new CardTypePredicate(CardType.INSTANT)); + filterSorcery.add(new CardTypePredicate(CardType.SORCERY)); + } + + public FinaleOfPromise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}"); + + // You may cast up to one target instant card and/or up to one target sorcery card from your graveyard + // each with converted mana cost X or less without paying their mana costs. + // If a card cast this way would be put into your graveyard this turn, exile it instead. + // If X is 10 or more, copy each of those spells twice. You may choose new targets for the copies. + this.getSpellAbility().addEffect(new FinaleOfPromiseEffect()); + this.getSpellAbility().setTargetAdjuster(FinaleOfPromiseAdjuster.instance); + } + + public FinaleOfPromise(final FinaleOfPromise card) { + super(card); + } + + @Override + public FinaleOfPromise copy() { + return new FinaleOfPromise(this); + } +} + +enum FinaleOfPromiseAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + + int xValue = ManacostVariableValue.instance.calculate(game, ability, null); + + // <= must be replaced to <= for html view + FilterCard filter1 = FinaleOfPromise.filterInstant.copy(); + filter1.setMessage("up to one INSTANT card from your graveyard with CMC <= " + xValue + " (target 1 of 2)"); + filter1.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, xValue + 1)); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter1)); + + FilterCard filter2 = FinaleOfPromise.filterSorcery.copy(); + filter2.setMessage("up to one SORCERY card from your graveyard with CMC <=" + xValue + " (target 2 of 2)"); + filter2.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, xValue + 1)); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter2)); + } +} + +class FinaleOfPromiseEffect extends OneShotEffect { + + public FinaleOfPromiseEffect() { + super(Outcome.PlayForFree); + this.staticText = "You may cast up to one target instant card and/or up to one target sorcery card from your graveyard " + + "each with converted mana cost X or less without paying their mana costs. If a card cast this way would " + + "be put into your graveyard this turn, exile it instead. If X is 10 or more, copy each of those spells " + + "twice. You may choose new targets for the copies."; + } + + public FinaleOfPromiseEffect(final FinaleOfPromiseEffect effect) { + super(effect); + } + + @Override + public FinaleOfPromiseEffect copy() { + return new FinaleOfPromiseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + // split card can be targeted two time -- but can cast only one + List cardsToCast = new ArrayList<>(); + for (Target target : source.getTargets()) { + for (UUID id : target.getTargets()) { + if (id != null && !cardsToCast.contains(id)) { + cardsToCast.add(id); + } + } + } + + // free cast + replace effect + for (UUID id : cardsToCast) { + Card card = game.getCard(id); + if (card != null) { + controller.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game)); + ContinuousEffect effect = new FinaleOfPromiseReplacementEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); + game.addEffect(effect, source); + } + } + + // If X is 10 or more, copy each of those spells twice. You may choose new targets for the copies + int xValue = ManacostVariableValue.instance.calculate(game, source, null); + if (xValue >= 10) { + for (UUID id : cardsToCast) { + Card card = game.getCard(id); + if (card != null) { + Spell spell = game.getStack().getSpell(card.getId()); + if (spell != null) { + spell.createCopyOnStack(game, source, controller.getId(), true); + spell.createCopyOnStack(game, source, controller.getId(), true); + game.informPlayers(controller.getLogName() + " copies " + spell.getName() + " twice."); + } + } + } + } + + return true; + } +} + +class FinaleOfPromiseReplacementEffect extends ReplacementEffectImpl { + + public FinaleOfPromiseReplacementEffect() { + super(Duration.EndOfTurn, Outcome.Exile); + staticText = "If a card cast this way would be put into your graveyard this turn, exile it instead"; + } + + public FinaleOfPromiseReplacementEffect(final FinaleOfPromiseReplacementEffect effect) { + super(effect); + } + + @Override + public FinaleOfPromiseReplacementEffect copy() { + return new FinaleOfPromiseReplacementEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card != null) { + card.moveToExile(null, "", source.getSourceId(), game); + return true; + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == 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(getTargetPointer().getFirst(game, source)); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfRevelation.java b/Mage.Sets/src/mage/cards/f/FinaleOfRevelation.java new file mode 100644 index 0000000000..afaf5aee13 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinaleOfRevelation.java @@ -0,0 +1,83 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.UntapLandsEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FinaleOfRevelation extends CardImpl { + + public FinaleOfRevelation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{U}{U}"); + + // Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game. + this.getSpellAbility().addEffect(new FinaleOfRevelationEffect()); + + // Exile Finale of Revelation. + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + private FinaleOfRevelation(final FinaleOfRevelation card) { + super(card); + } + + @Override + public FinaleOfRevelation copy() { + return new FinaleOfRevelation(this); + } +} + +class FinaleOfRevelationEffect extends OneShotEffect { + + FinaleOfRevelationEffect() { + super(Outcome.Benefit); + staticText = "Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, " + + "draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game."; + } + + private FinaleOfRevelationEffect(final FinaleOfRevelationEffect effect) { + super(effect); + } + + @Override + public FinaleOfRevelationEffect copy() { + return new FinaleOfRevelationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int xValue = source.getManaCostsToPay().getX(); + + if (xValue < 10) { + player.drawCards(xValue, game); + } else { + player.putCardsOnTopOfLibrary(player.getGraveyard(), game, source, false); + player.shuffleLibrary(source, game); + player.drawCards(xValue, game); + new UntapLandsEffect(5).apply(game, source); + game.addEffect(new MaximumHandSizeControllerEffect( + Integer.MAX_VALUE, Duration.EndOfGame, + MaximumHandSizeControllerEffect.HandSizeModification.SET + ), source); + } + + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FiremindVessel.java b/Mage.Sets/src/mage/cards/f/FiremindVessel.java new file mode 100644 index 0000000000..ca305fb40c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FiremindVessel.java @@ -0,0 +1,110 @@ +package mage.cards.f; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.ManaEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FiremindVessel extends CardImpl { + + public FiremindVessel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + // Firemind Vessel enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add two mana of different colors. + this.addAbility(new SimpleManaAbility( + Zone.BATTLEFIELD, new FiremindVesselManaEffect(), new TapSourceCost() + )); + } + + private FiremindVessel(final FiremindVessel card) { + super(card); + } + + @Override + public FiremindVessel copy() { + return new FiremindVessel(this); + } +} + +class FiremindVesselManaEffect extends ManaEffect { + + FiremindVesselManaEffect() { + super(); + staticText = "Add two mana of different colors."; + } + + private FiremindVesselManaEffect(final FiremindVesselManaEffect effect) { + super(effect); + } + + @Override + public Mana produceMana(boolean netMana, Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return null; + } + + ChoiceColor color1 = new ChoiceColor(true, "Choose color 1"); + if (!player.choose(outcome, color1, game) || color1.getColor() == null) { + return null; + } + + ChoiceColor color2 = new ChoiceColor(true, "Choose color 2"); + color2.removeColorFromChoices(color1.getChoice()); + if (!player.choose(outcome, color2, game) || color2.getColor() == null) { + return null; + } + + if (color1.getColor().equals(color2.getColor())) { + game.informPlayers("Player " + player.getName() + " is cheating with mana choices."); + return null; + } + + Mana mana = new Mana(); + mana.add(color1.getMana(1)); + mana.add(color2.getMana(1)); + return mana; + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + checkToFirePossibleEvents(getMana(game, source), game, source); + player.getManaPool().addMana(getMana(game, source), game, source); + return true; + } + return false; + } + + @Override + public List getNetMana(Game game, Ability source) { + ArrayList netMana = new ArrayList<>(); + netMana.add(new Mana(0, 0, 0, 0, 0, 0, 2, 0)); + return netMana; + } + + @Override + public FiremindVesselManaEffect copy() { + return new FiremindVesselManaEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FiremindsForesight.java b/Mage.Sets/src/mage/cards/f/FiremindsForesight.java index f7fb939a93..05b4376308 100644 --- a/Mage.Sets/src/mage/cards/f/FiremindsForesight.java +++ b/Mage.Sets/src/mage/cards/f/FiremindsForesight.java @@ -79,7 +79,7 @@ class FiremindsForesightSearchEffect extends OneShotEffect { cardsCount = cardsInLibrary.count(filter, game); if (cardsCount > 0) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { for (UUID cardId: target.getTargets()) { Card card = player.getLibrary().remove(cardId, game); if (card != null){ diff --git a/Mage.Sets/src/mage/cards/f/Fleshwrither.java b/Mage.Sets/src/mage/cards/f/Fleshwrither.java index 7fd31d85a2..a9f58feaad 100644 --- a/Mage.Sets/src/mage/cards/f/Fleshwrither.java +++ b/Mage.Sets/src/mage/cards/f/Fleshwrither.java @@ -74,7 +74,7 @@ class FleshwritherEffect extends OneShotEffect { FilterCreatureCard filter = new FilterCreatureCard("creature with converted mana cost " + sourceObject.getConvertedManaCost()); filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, sourceObject.getConvertedManaCost())); TargetCardInLibrary target = new TargetCardInLibrary(1, filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards chosen = new CardsImpl(target.getTargets()); controller.moveCards(chosen, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/f/FluxChanneler.java b/Mage.Sets/src/mage/cards/f/FluxChanneler.java index fa7ab3bceb..db14684b2f 100644 --- a/Mage.Sets/src/mage/cards/f/FluxChanneler.java +++ b/Mage.Sets/src/mage/cards/f/FluxChanneler.java @@ -24,7 +24,7 @@ public final class FluxChanneler extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); - // Whenever you cast a noncreature spell, proliferate. + // Whenever you cast a noncreature spell, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) this.addAbility(new SpellCastControllerTriggeredAbility( new ProliferateEffect(), StaticFilters.FILTER_SPELL_NON_CREATURE, false )); diff --git a/Mage.Sets/src/mage/cards/f/ForcedLanding.java b/Mage.Sets/src/mage/cards/f/ForcedLanding.java new file mode 100644 index 0000000000..fe46b1c5ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForcedLanding.java @@ -0,0 +1,42 @@ +package mage.cards.f; + +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ForcedLanding extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creature with flying"); + + static { + filter.add(new AbilityPredicate(FlyingAbility.class)); + } + + public ForcedLanding(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Put target creature with flying on the bottom of its owner's library. + this.getSpellAbility().addEffect(new PutOnLibraryTargetEffect(false)); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private ForcedLanding(final ForcedLanding card) { + super(card); + } + + @Override + public ForcedLanding copy() { + return new ForcedLanding(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/Foresee.java b/Mage.Sets/src/mage/cards/f/Foresee.java index d9e6c20b29..52b112e3a0 100644 --- a/Mage.Sets/src/mage/cards/f/Foresee.java +++ b/Mage.Sets/src/mage/cards/f/Foresee.java @@ -1,25 +1,23 @@ - - package mage.cards.f; -import java.util.UUID; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public final class Foresee extends CardImpl { public Foresee(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); - this.getSpellAbility().addEffect(new ScryEffect(4)); - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + this.getSpellAbility().addEffect(new ScryEffect(4).setText("scry 4,")); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).setText("then draw two cards")); } public Foresee(final Foresee card) { diff --git a/Mage.Sets/src/mage/cards/f/Foresight.java b/Mage.Sets/src/mage/cards/f/Foresight.java index 6f3ebfe1d5..296793c6bb 100644 --- a/Mage.Sets/src/mage/cards/f/Foresight.java +++ b/Mage.Sets/src/mage/cards/f/Foresight.java @@ -63,7 +63,7 @@ class ForesightEffect extends SearchEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null && player.searchLibrary(target, game)) { + if (player != null && player.searchLibrary(target, source, game)) { for (UUID targetId : getTargets()) { Card card = player.getLibrary().getCard(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/f/ForkInTheRoad.java b/Mage.Sets/src/mage/cards/f/ForkInTheRoad.java index d3f6c14c81..41be4c4330 100644 --- a/Mage.Sets/src/mage/cards/f/ForkInTheRoad.java +++ b/Mage.Sets/src/mage/cards/f/ForkInTheRoad.java @@ -66,7 +66,7 @@ class ForkInTheRoadEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/f/Frightcrawler.java b/Mage.Sets/src/mage/cards/f/Frightcrawler.java index d1123538e1..dcca2568ac 100644 --- a/Mage.Sets/src/mage/cards/f/Frightcrawler.java +++ b/Mage.Sets/src/mage/cards/f/Frightcrawler.java @@ -1,31 +1,27 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CardsInControllerGraveCondition; import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.combat.CantBlockSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.FearAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.*; + +import java.util.UUID; /** - * * @author cbt33 */ public final class Frightcrawler extends CardImpl { public Frightcrawler(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); this.subtype.add(SubType.HORROR); this.power = new MageInt(1); @@ -37,14 +33,14 @@ public final class Frightcrawler extends CardImpl { Ability thresholdAbility = new SimpleStaticAbility( Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), - new CardsInControllerGraveCondition(7), - "If seven or more cards are in your graveyard, {this} gets +2/+2 " + new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), + new CardsInControllerGraveCondition(7), + "If seven or more cards are in your graveyard, {this} gets +2/+2 " )); - thresholdAbility.addEffect(new ConditionalContinuousEffect( - new CantBlockSourceEffect(Duration.WhileOnBattlefield), - new CardsInControllerGraveCondition(7), - "and can't block.")); + thresholdAbility.addEffect(new ConditionalRestrictionEffect( + new CantBlockSourceEffect(Duration.WhileOnBattlefield), + new CardsInControllerGraveCondition(7), + "and can't block.")); thresholdAbility.setAbilityWord(AbilityWord.THRESHOLD); this.addAbility(thresholdAbility); } diff --git a/Mage.Sets/src/mage/cards/f/FromTheAshes.java b/Mage.Sets/src/mage/cards/f/FromTheAshes.java index 7adcaf8bd0..77d8390df9 100644 --- a/Mage.Sets/src/mage/cards/f/FromTheAshes.java +++ b/Mage.Sets/src/mage/cards/f/FromTheAshes.java @@ -81,7 +81,7 @@ class FromTheAshesEffect extends OneShotEffect { Player player = game.getPlayer(entry.getKey()); if (player != null && player.chooseUse(outcome, "Search your library for up to " + entry.getValue() + " basic land card(s) to put it onto the battlefield?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(0, entry.getValue(), StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/f/FuelForTheCause.java b/Mage.Sets/src/mage/cards/f/FuelForTheCause.java index a536fa6bc9..e60955e6e7 100644 --- a/Mage.Sets/src/mage/cards/f/FuelForTheCause.java +++ b/Mage.Sets/src/mage/cards/f/FuelForTheCause.java @@ -1,8 +1,5 @@ - - package mage.cards.f; -import java.util.UUID; import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.cards.CardImpl; @@ -10,21 +7,23 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.TargetSpell; +import java.util.UUID; + /** - * * @author Loki */ public final class FuelForTheCause extends CardImpl { - public FuelForTheCause (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{U}{U}"); + public FuelForTheCause(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{U}"); + // Counter target spell, then proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.getSpellAbility().addTarget(new TargetSpell()); this.getSpellAbility().addEffect(new CounterTargetEffect()); - this.getSpellAbility().addEffect(new ProliferateEffect()); + this.getSpellAbility().addEffect(new ProliferateEffect().concatBy(", then")); } - public FuelForTheCause (final FuelForTheCause card) { + public FuelForTheCause(final FuelForTheCause card) { super(card); } diff --git a/Mage.Sets/src/mage/cards/f/FuneralMarch.java b/Mage.Sets/src/mage/cards/f/FuneralMarch.java index e5b80c6489..9de029d752 100644 --- a/Mage.Sets/src/mage/cards/f/FuneralMarch.java +++ b/Mage.Sets/src/mage/cards/f/FuneralMarch.java @@ -1,7 +1,5 @@ - package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.ZoneChangeTriggeredAbility; import mage.abilities.effects.Effect; @@ -23,14 +21,15 @@ import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author L_J */ public final class FuneralMarch extends CardImpl { public FuneralMarch(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}"); this.subtype.add(SubType.AURA); // Enchant creature @@ -73,17 +72,19 @@ class FuneralMarchTriggeredAbility extends ZoneChangeTriggeredAbility { public boolean checkTrigger(GameEvent event, Game game) { Permanent enchantment = game.getPermanentOrLKIBattlefield(this.getSourceId()); if (enchantment != null && enchantment.getAttachedTo() != null && event.getTargetId().equals(enchantment.getAttachedTo())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if ((fromZone == null || zEvent.getFromZone() == fromZone) && (toZone == null || zEvent.getToZone() == toZone)) { - for (Effect effect : getEffects()) { - if (zEvent.getTarget() != null) { - Permanent attachedTo = (Permanent) game.getLastKnownInformation(enchantment.getAttachedTo(), Zone.BATTLEFIELD, enchantment.getAttachedToZoneChangeCounter()); - if (attachedTo != null) { - effect.setTargetPointer(new FixedTarget(attachedTo.getControllerId())); + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if ((fromZone == null || zEvent.getFromZone() == fromZone) && (toZone == null || zEvent.getToZone() == toZone)) { + for (Effect effect : getEffects()) { + if (zEvent.getTarget() != null) { + Permanent attachedTo = (Permanent) game.getLastKnownInformation(enchantment.getAttachedTo(), Zone.BATTLEFIELD, enchantment.getAttachedToZoneChangeCounter()); + if (attachedTo != null) { + effect.setTargetPointer(new FixedTarget(attachedTo.getControllerId())); + } } - } + } + return true; } - return true; } } return false; diff --git a/Mage.Sets/src/mage/cards/f/Fylgja.java b/Mage.Sets/src/mage/cards/f/Fylgja.java new file mode 100644 index 0000000000..49df567747 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/Fylgja.java @@ -0,0 +1,66 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.PreventDamageToAttachedEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public final class Fylgja extends CardImpl { + + public Fylgja(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Fylgja enters the battlefield with four healing counters on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.HEALING.createInstance(4)) + .setText("with four healing counters on it."))); + + // Remove a healing counter from Fylgja: Prevent the next 1 damage that would be dealt to enchanted creature this turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreventDamageToAttachedEffect(Duration.EndOfTurn, AttachmentType.AURA, 1, false) + .setText("Prevent the next 1 damage that would be dealt to enchanted creature this turn"), + new RemoveCountersSourceCost(CounterType.HEALING.createInstance(1)))); + + // {2}{W}: Put a healing counter on Fylgja. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.HEALING.createInstance(1)), + new ManaCostsImpl("{2}{W}"))); + } + + private Fylgja(final Fylgja card) { + super(card); + } + + @Override + public Fylgja copy() { + return new Fylgja(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GarrukTheVeilCursed.java b/Mage.Sets/src/mage/cards/g/GarrukTheVeilCursed.java index 55c2665e2f..26c6b633b1 100644 --- a/Mage.Sets/src/mage/cards/g/GarrukTheVeilCursed.java +++ b/Mage.Sets/src/mage/cards/g/GarrukTheVeilCursed.java @@ -145,7 +145,7 @@ class GarrukTheVeilCursedEffect extends OneShotEffect { FilterCreatureCard filter = new FilterCreatureCard(); TargetCardInLibrary targetInLibrary = new TargetCardInLibrary(filter); Cards cards = new CardsImpl(); - if (controller.searchLibrary(targetInLibrary, game)) { + if (controller.searchLibrary(targetInLibrary, source, game)) { for (UUID cardId : targetInLibrary.getTargets()) { Card card = controller.getLibrary().remove(cardId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/g/GateToTheAfterlife.java b/Mage.Sets/src/mage/cards/g/GateToTheAfterlife.java index 5ffff38827..cd375ad4f5 100644 --- a/Mage.Sets/src/mage/cards/g/GateToTheAfterlife.java +++ b/Mage.Sets/src/mage/cards/g/GateToTheAfterlife.java @@ -123,7 +123,7 @@ class GateToTheAfterlifeEffect extends OneShotEffect { if (card == null && controller.chooseUse(Outcome.Benefit, "Do you want to search your library for " + cardName + "?", source, game)) { librarySearched = true; TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { card = game.getCard(target.getFirstTarget()); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/g/GemOfBecoming.java b/Mage.Sets/src/mage/cards/g/GemOfBecoming.java index 294b640c8d..64e607e0f8 100644 --- a/Mage.Sets/src/mage/cards/g/GemOfBecoming.java +++ b/Mage.Sets/src/mage/cards/g/GemOfBecoming.java @@ -85,7 +85,7 @@ class GemOfBecomingEffect extends OneShotEffect { FilterLandCard filter = new FilterLandCard(subtype); filter.add(new SubtypePredicate(SubType.byDescription(subtype))); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { card.moveToZone(Zone.HAND, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/g/GhostQuarter.java b/Mage.Sets/src/mage/cards/g/GhostQuarter.java index 89e6e43128..881f2da242 100644 --- a/Mage.Sets/src/mage/cards/g/GhostQuarter.java +++ b/Mage.Sets/src/mage/cards/g/GhostQuarter.java @@ -73,7 +73,7 @@ class GhostQuarterEffect extends OneShotEffect { Player controller = game.getPlayer(permanent.getControllerId()); if (controller != null && controller.chooseUse(Outcome.PutLandInPlay, "Do you wish to search for a basic land, put it onto the battlefield and then shuffle your library?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/g/GideonBattleForged.java b/Mage.Sets/src/mage/cards/g/GideonBattleForged.java index f54e3445b7..13b4d2d8ba 100644 --- a/Mage.Sets/src/mage/cards/g/GideonBattleForged.java +++ b/Mage.Sets/src/mage/cards/g/GideonBattleForged.java @@ -1,7 +1,5 @@ - package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -16,12 +14,7 @@ import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.constants.TurnPhase; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; @@ -29,8 +22,9 @@ import mage.game.permanent.Permanent; import mage.game.permanent.token.TokenImpl; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class GideonBattleForged extends CardImpl { @@ -42,7 +36,7 @@ public final class GideonBattleForged extends CardImpl { } public GideonBattleForged(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.PLANESWALKER},""); + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.GIDEON); @@ -98,6 +92,7 @@ class GideonBattleForgedToken extends TokenImpl { toughness = new MageInt(4); this.addAbility(IndestructibleAbility.getInstance()); } + public GideonBattleForgedToken(final GideonBattleForgedToken token) { super(token); } @@ -109,7 +104,6 @@ class GideonBattleForgedToken extends TokenImpl { class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { - int nextTurnTargetController = 0; protected MageObjectReference targetPermanentReference; public GideonBattleForgedAttacksIfAbleTargetEffect(Duration duration) { @@ -119,7 +113,6 @@ class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { public GideonBattleForgedAttacksIfAbleTargetEffect(final GideonBattleForgedAttacksIfAbleTargetEffect effect) { super(effect); - this.nextTurnTargetController = effect.nextTurnTargetController; this.targetPermanentReference = effect.targetPermanentReference; } @@ -137,10 +130,7 @@ class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { if (targetPermanent == null) { return true; } - if (nextTurnTargetController == 0 && startingTurn != game.getTurnNum() && game.isActivePlayer(targetPermanent.getControllerId())) { - nextTurnTargetController = game.getTurnNum(); - } - return game.getPhase().getType() == TurnPhase.END && nextTurnTargetController > 0 && game.getTurnNum() > nextTurnTargetController; + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); // discard on end of their next turn } @Override diff --git a/Mage.Sets/src/mage/cards/g/GideonBlackblade.java b/Mage.Sets/src/mage/cards/g/GideonBlackblade.java new file mode 100644 index 0000000000..f57cd82e97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GideonBlackblade.java @@ -0,0 +1,167 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalPreventionEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.PreventAllDamageToSourceEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TokenImpl; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetNonlandPermanent; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GideonBlackblade extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another other creature you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public GideonBlackblade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.GIDEON); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // As long as it's your turn, Gideon Blackblade is a 4/4 Human Soldier creature with indestructible that's still a planeswalker. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BecomesCreatureSourceEffect( + new GideonBlackbladeToken(), "planeswalker", Duration.WhileOnBattlefield + ), MyTurnCondition.instance, "As long as it's your turn, " + + "{this} is a 4/4 Human Soldier creature with indestructible that's still a planeswalker." + ))); + + // Prevent all damage that would be dealt to Gideon Blackblade during your turn. + this.addAbility(new SimpleStaticAbility(new ConditionalPreventionEffect( + new PreventAllDamageToSourceEffect(Duration.WhileOnBattlefield), + MyTurnCondition.instance, "Prevent all damage that would be dealt to {this} during your turn." + ))); + + // +1: Up to one other target creature you control gains your choice of vigilance, lifelink, or indestructible until end of turn. + Ability ability = new LoyaltyAbility(new GideonBlackbladeEffect(), 1); + ability.addTarget(new TargetPermanent(0, 1, filter, false)); + this.addAbility(ability); + + // -6: Exile target nonland permanent. + ability = new LoyaltyAbility(new ExileTargetEffect(), -6); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); + } + + private GideonBlackblade(final GideonBlackblade card) { + super(card); + } + + @Override + public GideonBlackblade copy() { + return new GideonBlackblade(this); + } +} + +class GideonBlackbladeToken extends TokenImpl { + + GideonBlackbladeToken() { + super("", "4/4 Human Soldier creature"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.HUMAN); + subtype.add(SubType.SOLDIER); + power = new MageInt(4); + toughness = new MageInt(4); + this.addAbility(IndestructibleAbility.getInstance()); + } + + private GideonBlackbladeToken(final GideonBlackbladeToken token) { + super(token); + } + + @Override + public GideonBlackbladeToken copy() { + return new GideonBlackbladeToken(this); + } +} + +class GideonBlackbladeEffect extends OneShotEffect { + private static final Set choices = new HashSet(); + + static { + choices.add("Vigilance"); + choices.add("Lifelink"); + choices.add("Indestructible"); + } + + GideonBlackbladeEffect() { + super(Outcome.Benefit); + staticText = "Up to one other target creature you control gains your choice of " + + "vigilance, lifelink, or indestructible until end of turn."; + } + + private GideonBlackbladeEffect(final GideonBlackbladeEffect effect) { + super(effect); + } + + @Override + public GideonBlackbladeEffect copy() { + return new GideonBlackbladeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (player == null || permanent == null) { + return false; + } + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose an ability to give to " + permanent.getLogName()); + choice.setChoices(choices); + if (!player.choose(outcome, choice, game)) { + return false; + } + Ability ability = null; + switch (choice.getChoice()) { + case "Vigilance": + ability = VigilanceAbility.getInstance(); + break; + case "Lifelink": + ability = LifelinkAbility.getInstance(); + break; + case "Indestructible": + ability = IndestructibleAbility.getInstance(); + break; + } + if (ability != null) { + game.addEffect(new GainAbilityTargetEffect(ability, Duration.EndOfTurn), source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GideonJura.java b/Mage.Sets/src/mage/cards/g/GideonJura.java index 12c783c9ed..4aa287a468 100644 --- a/Mage.Sets/src/mage/cards/g/GideonJura.java +++ b/Mage.Sets/src/mage/cards/g/GideonJura.java @@ -1,6 +1,5 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -13,11 +12,7 @@ import mage.abilities.effects.common.PreventAllDamageToSourceEffect; import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; 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.constants.TurnPhase; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -26,8 +21,9 @@ import mage.game.permanent.token.TokenImpl; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public final class GideonJura extends CardImpl { @@ -118,20 +114,21 @@ class GideonJuraEffect extends RequirementEffect { public void init(Ability source, Game game) { super.init(source, game); creatingPermanent = new MageObjectReference(source.getSourceId(), game); + setStartingControllerAndTurnNum(game, source.getFirstTarget(), game.getActivePlayerId()); // setup startingController to calc isYourTurn calls } @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.isControlledBy(source.getFirstTarget()); + return permanent.isControlledBy(source.getFirstTarget()) && this.isYourNextTurn(game); } @Override public boolean isInactive(Ability source, Game game) { - return (startingTurn != game.getTurnNum() - && (game.getPhase().getType() == TurnPhase.END - && game.isActivePlayer(source.getFirstTarget()))) - || // 6/15/2010: If a creature controlled by the affected player can't attack Gideon Jura (because he's no longer on the battlefield, for example), that player may have it attack you, another one of your planeswalkers, or nothing at all. - creatingPermanent.getPermanent(game) == null; + return (game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game)) + // 6/15/2010: If a creature controlled by the affected player can't attack Gideon Jura + // (because he's no longer on the battlefield, for example), that player may have it attack you, + // another one of your planeswalkers, or nothing at all. + || creatingPermanent.getPermanent(game) == null; } @Override diff --git a/Mage.Sets/src/mage/cards/g/GideonTheOathsworn.java b/Mage.Sets/src/mage/cards/g/GideonTheOathsworn.java new file mode 100644 index 0000000000..d1ab66a505 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GideonTheOathsworn.java @@ -0,0 +1,167 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileAllEffect; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.PreventAllDamageToSourceEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TokenImpl; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GideonTheOathsworn extends CardImpl { + + public GideonTheOathsworn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.GIDEON); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Whenever you attack with two or more non-Gideon creatures, put a +1/+1 counter on each of those creatures. + this.addAbility(new GideonTheOathswornTriggeredAbility()); + + // +2: Until end of turn, Gideon, the Oathsworn becomes a 5/5 white Soldier creature that's still a planeswalker. Prevent all damage that would be dealt to him this turn. + Ability ability = new LoyaltyAbility(new BecomesCreatureSourceEffect( + new GideonTheOathswornToken(), "planeswalker", Duration.EndOfTurn + ), 2); + ability.addEffect(new PreventAllDamageToSourceEffect( + Duration.EndOfTurn + ).setText("Prevent all damage that would be dealt to him this turn")); + this.addAbility(ability); + + // -9: Exile Gideon, the Oathsworn and each creature your opponents control. + ability = new LoyaltyAbility(new ExileSourceEffect(), -9); + ability.addEffect(new ExileAllEffect( + StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE + ).setText("and each creature your opponents control")); + this.addAbility(ability); + } + + private GideonTheOathsworn(final GideonTheOathsworn card) { + super(card); + } + + @Override + public GideonTheOathsworn copy() { + return new GideonTheOathsworn(this); + } +} + +class GideonTheOathswornTriggeredAbility extends TriggeredAbilityImpl { + + GideonTheOathswornTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + } + + private GideonTheOathswornTriggeredAbility(final GideonTheOathswornTriggeredAbility ability) { + super(ability); + } + + @Override + public GideonTheOathswornTriggeredAbility copy() { + return new GideonTheOathswornTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (game.getCombat().getAttackingPlayerId().equals(getControllerId())) { + int attackerCount = 0; + Set attackers = new HashSet(); + for (UUID attackerId : game.getCombat().getAttackers()) { + Permanent permanent = game.getPermanent(attackerId); + if (permanent != null && permanent.isCreature() && !permanent.hasSubtype(SubType.GIDEON, game)) { + attackerCount++; + attackers.add(new MageObjectReference(permanent, game)); + } + } + if (attackerCount >= 2) { + this.getEffects().clear(); + this.addEffect(new GideonTheOathswornEffect(attackers)); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you attack with two or more non-Gideon creatures, " + + "put a +1/+1 counter on each of those creatures."; + } +} + +class GideonTheOathswornEffect extends OneShotEffect { + + private final Set attackers; + + GideonTheOathswornEffect(Set attackers) { + super(Outcome.Benefit); + this.attackers = attackers; + } + + private GideonTheOathswornEffect(final GideonTheOathswornEffect effect) { + super(effect); + this.attackers = effect.attackers; + } + + @Override + public GideonTheOathswornEffect copy() { + return new GideonTheOathswornEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (MageObjectReference mor : attackers) { + Permanent permanent = mor.getPermanent(game); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + } + } + return true; + } +} + +class GideonTheOathswornToken extends TokenImpl { + + GideonTheOathswornToken() { + super("", "5/5 white Soldier creature"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.SOLDIER); + power = new MageInt(5); + toughness = new MageInt(5); + } + + private GideonTheOathswornToken(final GideonTheOathswornToken token) { + super(token); + } + + @Override + public GideonTheOathswornToken copy() { + return new GideonTheOathswornToken(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GideonsBattleCry.java b/Mage.Sets/src/mage/cards/g/GideonsBattleCry.java new file mode 100644 index 0000000000..7a48f5008d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GideonsBattleCry.java @@ -0,0 +1,46 @@ +package mage.cards.g; + +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.common.search.SearchLibraryGraveyardPutInHandEffect; +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.filter.predicate.mageobject.NamePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GideonsBattleCry extends CardImpl { + + private static final FilterCard filter = new FilterCard("Gideon, the Oathsworn"); + + static { + filter.add(new NamePredicate("Gideon, the Oathsworn")); + } + + public GideonsBattleCry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}{W}"); + + // Put a +1/+1 counter on each creature you control. You may search your library and/or graveyard for a card named Gideon, the Oathsworn, reveal it, and put it into your hand. If you search your library this way, shuffle it. + this.getSpellAbility().addEffect(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + )); + this.getSpellAbility().addEffect(new SearchLibraryGraveyardPutInHandEffect( + filter, false, true + )); + } + + private GideonsBattleCry(final GideonsBattleCry card) { + super(card); + } + + @Override + public GideonsBattleCry copy() { + return new GideonsBattleCry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GideonsCompany.java b/Mage.Sets/src/mage/cards/g/GideonsCompany.java new file mode 100644 index 0000000000..f5e3a03f7d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GideonsCompany.java @@ -0,0 +1,57 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.GainLifeControllerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.FilterPlaneswalkerPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GideonsCompany extends CardImpl { + + private static final FilterPermanent filter = new FilterPlaneswalkerPermanent(SubType.GIDEON); + + public GideonsCompany(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you gain life, put two +1/+1 counters on Gideon's Company. + this.addAbility(new GainLifeControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false + )); + + // {3}{W}: Put a loyalty counter on target Gideon planeswalker. + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.LOYALTY.createInstance()), new ManaCostsImpl("{3}{W}") + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private GideonsCompany(final GideonsCompany card) { + super(card); + } + + @Override + public GideonsCompany copy() { + return new GideonsCompany(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GideonsSacrifice.java b/Mage.Sets/src/mage/cards/g/GideonsSacrifice.java new file mode 100644 index 0000000000..4bdccaf0aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GideonsSacrifice.java @@ -0,0 +1,181 @@ +package mage.cards.g; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GideonsSacrifice extends CardImpl { + + public GideonsSacrifice(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Choose a creature or planeswalker you control. All damage that would be dealt this turn to you and permanents you control is dealt to the chosen permanent instead. + this.getSpellAbility().addEffect(new GideonsSacrificeEffect()); + } + + private GideonsSacrifice(final GideonsSacrifice card) { + super(card); + } + + @Override + public GideonsSacrifice copy() { + return new GideonsSacrifice(this); + } +} + +class GideonsSacrificeEffect extends OneShotEffect { + + private static final FilterPermanent filter + = new FilterControlledPermanent("creature or planeswalker you controls"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.PLANESWALKER), + new CardTypePredicate(CardType.CREATURE) + )); + } + + GideonsSacrificeEffect() { + super(Outcome.Benefit); + staticText = "Choose a creature or planeswalker you control. " + + "All damage that would be dealt this turn to you " + + "and permanents you control is dealt to the chosen permanent instead."; + } + + private GideonsSacrificeEffect(final GideonsSacrificeEffect effect) { + super(effect); + } + + @Override + public GideonsSacrificeEffect copy() { + return new GideonsSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Target target = new TargetPermanent(filter); + target.setNotTarget(true); + if (!player.choose(outcome, target, source.getSourceId(), game)) { + return false; + } + game.addEffect(new GideonsSacrificeEffectReplacementEffect( + new MageObjectReference(target.getFirstTarget(), game) + ), source); + return true; + } +} + +class GideonsSacrificeEffectReplacementEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + GideonsSacrificeEffectReplacementEffect(MageObjectReference mor) { + super(Duration.EndOfTurn, Outcome.RedirectDamage); + this.mor = mor; + } + + private GideonsSacrificeEffectReplacementEffect(final GideonsSacrificeEffectReplacementEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_CREATURE: + case DAMAGE_PLAYER: + case DAMAGE_PLANESWALKER: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getType() == GameEvent.EventType.DAMAGE_PLAYER + && event.getPlayerId().equals(source.getControllerId())) { + return true; + } + if (event.getType() == GameEvent.EventType.DAMAGE_CREATURE + || event.getType() == GameEvent.EventType.DAMAGE_PLANESWALKER) { + Permanent targetPermanent = game.getPermanent(event.getTargetId()); + if (targetPermanent != null + && targetPermanent.isControlledBy(source.getControllerId())) { + return true; + } + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + DamageEvent damageEvent = (DamageEvent) event; + + Permanent permanent = mor.getPermanent(game); + + if (permanent == null) { + return false; + } + + // Name of old target + Permanent targetPermanent = game.getPermanent(event.getTargetId()); + StringBuilder message = new StringBuilder(); + message.append(permanent.getName()).append(": gets "); + message.append(damageEvent.getAmount()).append(" damage redirected from "); + if (targetPermanent != null) { + message.append(targetPermanent.getName()); + } else { + Player targetPlayer = game.getPlayer(event.getTargetId()); + if (targetPlayer != null) { + message.append(targetPlayer.getLogName()); + } else { + message.append("unknown"); + } + + } + game.informPlayers(message.toString()); + // Redirect damage + permanent.damage( + damageEvent.getAmount(), damageEvent.getSourceId(), game, + damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects() + ); + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public GideonsSacrificeEffectReplacementEffect copy() { + return new GideonsSacrificeEffectReplacementEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GideonsTriumph.java b/Mage.Sets/src/mage/cards/g/GideonsTriumph.java index b8a7b117de..cd8c9b6242 100644 --- a/Mage.Sets/src/mage/cards/g/GideonsTriumph.java +++ b/Mage.Sets/src/mage/cards/g/GideonsTriumph.java @@ -34,6 +34,7 @@ public final class GideonsTriumph extends CardImpl { // Target opponent sacrifices a creature that attacked or blocked this turn. If you control a Gideon planeswalker, that player sacrifices two of those creatures instead. this.getSpellAbility().addEffect(new GideonsTriumphEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); + this.getSpellAbility().addWatcher(new GideonsTriumphWatcher()); } private GideonsTriumph(final GideonsTriumph card) { @@ -48,11 +49,15 @@ public final class GideonsTriumph extends CardImpl { class GideonsTriumphEffect extends OneShotEffect { - private static final FilterControlledPlaneswalkerPermanent filter + private static final FilterControlledPlaneswalkerPermanent filterGideon = new FilterControlledPlaneswalkerPermanent(SubType.GIDEON); - private static final FilterPermanent filter2 + private static final FilterPermanent filterSacrifice = new FilterPermanent("creature that attacked or blocked this turn"); + static { + filterSacrifice.add(GideonsTriumphPredicate.instance); + } + GideonsTriumphEffect() { super(Outcome.Benefit); staticText = "Target opponent sacrifices a creature that attacked or blocked this turn. " + @@ -71,14 +76,14 @@ class GideonsTriumphEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int count = 1; - if (!game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game).isEmpty()) { + if (!game.getBattlefield().getActivePermanents(filterGideon, source.getControllerId(), game).isEmpty()) { count++; } - return new SacrificeEffect(filter2, count, "Target opponent").apply(game, source); + return new SacrificeEffect(filterSacrifice, count, "Target opponent").apply(game, source); } } -enum GideonsTriumphCondition implements Predicate { +enum GideonsTriumphPredicate implements Predicate { instance; @Override @@ -118,4 +123,9 @@ class GideonsTriumphWatcher extends Watcher { return new GideonsTriumphWatcher(this); } + @Override + public void reset() { + attackedOrBlockedThisTurnCreatures.clear(); + } + } diff --git a/Mage.Sets/src/mage/cards/g/GiftOfTheWoods.java b/Mage.Sets/src/mage/cards/g/GiftOfTheWoods.java new file mode 100644 index 0000000000..185f1a9eb7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GiftOfTheWoods.java @@ -0,0 +1,54 @@ + +package mage.cards.g; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BlocksOrBecomesBlockedTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Ketsuban + */ +public final class GiftOfTheWoods extends CardImpl { + + public GiftOfTheWoods(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.ENCHANTMENT }, "{G}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); + this.getSpellAbility().addTarget(auraTarget); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Whenever enchanted creature blocks or becomes blocked, it gets +0/+3 until + // end of turn and you gain 1 life. + Ability ability2 = new BlocksOrBecomesBlockedTriggeredAbility( + new BoostEnchantedEffect(0, 3, Duration.EndOfTurn), false); + ability2.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability2); + } + + public GiftOfTheWoods(final GiftOfTheWoods card) { + super(card); + } + + @Override + public GiftOfTheWoods copy() { + return new GiftOfTheWoods(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java index 154e932ad9..c99f4c54f1 100644 --- a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java +++ b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java @@ -67,7 +67,7 @@ class GiftsUngivenEffect extends OneShotEffect { return false; } GiftsUngivenTarget target = new GiftsUngivenTarget(); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/g/Gigantiform.java b/Mage.Sets/src/mage/cards/g/Gigantiform.java index 35c5f5742d..f20cfb120c 100644 --- a/Mage.Sets/src/mage/cards/g/Gigantiform.java +++ b/Mage.Sets/src/mage/cards/g/Gigantiform.java @@ -113,7 +113,7 @@ class GigantiformEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller != null && controller.searchLibrary(target, game)) { + if (controller != null && controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/g/GodEternalBontu.java b/Mage.Sets/src/mage/cards/g/GodEternalBontu.java new file mode 100644 index 0000000000..ab11688724 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GodEternalBontu.java @@ -0,0 +1,101 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.GodEternalDiesTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +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.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GodEternalBontu extends CardImpl { + + public GodEternalBontu(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.GOD); + this.power = new MageInt(5); + this.toughness = new MageInt(6); + + // Menace + this.addAbility(new MenaceAbility()); + + // When God-Eternal Bontu enters the battlefield, sacrifice any number of other permanents, then draw that many cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GodEternalBontuEffect())); + + // When God-Eternal Bontu dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. + this.addAbility(new GodEternalDiesTriggeredAbility()); + } + + private GodEternalBontu(final GodEternalBontu card) { + super(card); + } + + @Override + public GodEternalBontu copy() { + return new GodEternalBontu(this); + } +} + +class GodEternalBontuEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent("other permanents you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + GodEternalBontuEffect() { + super(Outcome.Benefit); + staticText = "sacrifice any number of other permanents, then draw that many cards."; + } + + private GodEternalBontuEffect(final GodEternalBontuEffect effect) { + super(effect); + } + + @Override + public GodEternalBontuEffect copy() { + return new GodEternalBontuEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Target target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true); + if (!player.choose(outcome, target, source.getSourceId(), game)) { + return false; + } + int counter = 0; + for (UUID permanentId : target.getTargets()) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null && permanent.sacrifice(source.getSourceId(), game)) { + counter++; + } + } + return player.drawCards(counter, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GodEternalKefnet.java b/Mage.Sets/src/mage/cards/g/GodEternalKefnet.java new file mode 100644 index 0000000000..8e1ce4c277 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GodEternalKefnet.java @@ -0,0 +1,157 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.GodEternalDiesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.HintUtils; +import mage.abilities.keyword.FlyingAbility; +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.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.common.CardsAmountDrawnThisTurnWatcher; + +import java.awt.*; +import java.util.UUID; + +/** + * @author JayDi85 + */ +public final class GodEternalKefnet extends CardImpl { + + public GodEternalKefnet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.GOD); + + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, + // copy that card and you may cast the copy. That copy costs {2} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GodEternalKefnetDrawCardReplacementEffect()), new CardsAmountDrawnThisTurnWatcher()); + + // When God-Eternal Kefnet dies or is put into exile from the battlefield, you may put it into its owner’s library third from the top. + this.addAbility(new GodEternalDiesTriggeredAbility()); + } + + public GodEternalKefnet(final GodEternalKefnet card) { + super(card); + } + + @Override + public GodEternalKefnet copy() { + return new GodEternalKefnet(this); + } +} + +class GodEternalKefnetDrawCardReplacementEffect extends ReplacementEffectImpl { + + public GodEternalKefnetDrawCardReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral); + this.staticText = "You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant " + + "or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast"; + } + + public GodEternalKefnetDrawCardReplacementEffect(final GodEternalKefnetDrawCardReplacementEffect effect) { + super(effect); + } + + @Override + public GodEternalKefnetDrawCardReplacementEffect copy() { + return new GodEternalKefnetDrawCardReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + // reveal top card and drawn (return false to continue default draw) + Permanent god = game.getPermanent(source.getSourceId()); + Player you = game.getPlayer(source.getControllerId()); + if (god == null && you == null) { + return false; + } + + Card topCard = you.getLibrary().getTopCards(game, 1).iterator().next(); + if (topCard == null) { + return false; + } + + // reveal + you.setTopCardRevealed(true); + + // cast copy + if (topCard.isInstantOrSorcery() && you.chooseUse(outcome, "Would you like to copy " + topCard.getName() + + " and cast it {2} less?", source, game)) { + Card blueprint = topCard.copy(); + blueprint.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(2))); + Card copiedCard = game.copyCard(blueprint, source, source.getControllerId()); + you.moveCardToHandWithInfo(copiedCard, source.getSourceId(), game, true); // The copy is created in and cast from your hand. + you.cast(copiedCard.getSpellAbility(), game, false, new MageObjectReference(source.getSourceObject(game), game)); + } + + // draw (return false for default draw) + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == EventType.DRAW_CARD; + } + + String getAppliedMark(Game game, Ability source) { + return source.getId() + "-applied-" + source.getControllerId() + "-" + game.getState().getTurnNum(); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!event.getPlayerId().equals(source.getControllerId())) { + return false; + } + + Permanent god = game.getPermanent(source.getSourceId()); + Player you = game.getPlayer(source.getControllerId()); + if (god == null && you == null) { + return false; + } + + Card topCard = you.getLibrary().getTopCards(game, 1).iterator().next(); + if (topCard == null) { + return false; + } + + // only first draw card + // if card casted on that turn or controlled changed then needs history from watcher + CardsAmountDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsAmountDrawnThisTurnWatcher.class); + if (watcher != null && watcher.getAmountCardsDrawn(event.getPlayerId()) != 0) { + return false; + } + + // one time use (if multiple cards drawn) + String mark = getAppliedMark(game, source); + if (game.getState().getValue(mark) != null) { + return false; + } + game.getState().setValue(mark, true); + + // ask player to reveal top cards + String mes = topCard.getName() + ", " + (topCard.isInstantOrSorcery() + ? HintUtils.prepareText("you can copy it and cast {2} less", Color.green) + : HintUtils.prepareText("you can't copy it", Color.red)); + return you.chooseUse(Outcome.Benefit, "Would you like to reveal first drawn card (" + mes + ")?", source, game); + } + +} + diff --git a/Mage.Sets/src/mage/cards/g/GodEternalOketra.java b/Mage.Sets/src/mage/cards/g/GodEternalOketra.java new file mode 100644 index 0000000000..5e297e10a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GodEternalOketra.java @@ -0,0 +1,53 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.GodEternalDiesTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +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 mage.game.permanent.token.GodEternalOketraToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GodEternalOketra extends CardImpl { + + public GodEternalOketra(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.GOD); + this.power = new MageInt(3); + this.toughness = new MageInt(6); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Whenever you cast a creature spell, create a 4/4 black Zombie Warrior creature token with vigilance. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new GodEternalOketraToken()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + )); + + // When God-Eternal Oketra dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. + this.addAbility(new GodEternalDiesTriggeredAbility()); + } + + private GodEternalOketra(final GodEternalOketra card) { + super(card); + } + + @Override + public GodEternalOketra copy() { + return new GodEternalOketra(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java b/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java new file mode 100644 index 0000000000..4ba9c292ad --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GodEternalRhonas.java @@ -0,0 +1,101 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +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.keyword.DeathtouchAbility; +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.targetpointer.FixedTarget; + +import java.util.UUID; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.filter.common.FilterControlledCreaturePermanent; + +/** + * @author TheElk801 + */ +public final class GodEternalRhonas extends CardImpl { + + public GodEternalRhonas(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.GOD); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // When God-Eternal Rhonas enters the battlefield, double the power of each other creature you control until end of turn. Those creatures gain vigilance until end of turn. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GodEternalRhonasEffect())); + + // When God-Eternal Rhonas dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. + this.addAbility(new GodEternalDiesTriggeredAbility()); + } + + private GodEternalRhonas(final GodEternalRhonas card) { + super(card); + } + + @Override + public GodEternalRhonas copy() { + return new GodEternalRhonas(this); + } +} + +class GodEternalRhonasEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + GodEternalRhonasEffect() { + super(Outcome.Benefit); + staticText = "double the power of each other creature you control until end of turn. " + + "Those creatures gain vigilance until end of turn."; + } + + private GodEternalRhonasEffect(final GodEternalRhonasEffect effect) { + super(effect); + } + + @Override + public GodEternalRhonasEffect copy() { + return new GodEternalRhonasEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent godEternalRhonas = game.getPermanent(source.getSourceId()); + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) { + if (permanent == null + || godEternalRhonas != null + && permanent == godEternalRhonas) { + continue; + } + ContinuousEffect effect = new BoostTargetEffect( + permanent.getPower().getValue(), + 0, Duration.EndOfTurn + ); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + + ContinuousEffect effect2 = new GainAbilityTargetEffect( + VigilanceAbility.getInstance(), + Duration.EndOfTurn + ); + effect2.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect2, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoldenDemise.java b/Mage.Sets/src/mage/cards/g/GoldenDemise.java index 91641ac77d..f4a14a8c98 100644 --- a/Mage.Sets/src/mage/cards/g/GoldenDemise.java +++ b/Mage.Sets/src/mage/cards/g/GoldenDemise.java @@ -5,6 +5,7 @@ import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.keyword.AscendEffect; import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.PermanentsYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,8 +24,10 @@ public final class GoldenDemise extends CardImpl { public GoldenDemise(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); - // Ascend + // Ascend (If you control ten or more permanents, you get the city’s blessing for the rest of the game.) this.getSpellAbility().addEffect(new AscendEffect()); + this.getSpellAbility().addHint(CitysBlessingHint.instance); + this.getSpellAbility().addHint(PermanentsYouControlHint.instance); // All creatures get -2/-2 until end of turn. If you have the city's blessing, instead only creatures your opponents control get -2/-2 until end of turn. FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures your opponents control"); @@ -35,7 +38,6 @@ public final class GoldenDemise extends CardImpl { CitysBlessingCondition.instance, "All creatures get -2/-2 until end of turn. If you have the city's blessing, instead only creatures your opponents control get -2/-2 until end of turn" )); - this.getSpellAbility().addHint(CitysBlessingHint.instance); } public GoldenDemise(final GoldenDemise card) { diff --git a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java index cae0b23530..0755e3a282 100644 --- a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java +++ b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java @@ -184,7 +184,15 @@ class GontiLordOfLuxurySpendAnyManaEffect extends AsThoughEffectImpl implements @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + Card theCard = game.getCard(objectId); + if(theCard == null){ + return false; + } + Card mainCard = theCard.getMainCard(); + if(mainCard == null){ + return false; + } + objectId = mainCard.getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { if (affectedControllerId.equals(source.getControllerId())) { @@ -229,7 +237,15 @@ class GontiLordOfLuxuryLookEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + Card theCard = game.getCard(objectId); + if(theCard == null){ + return false; + } + Card mainCard = theCard.getMainCard(); + if(mainCard == null){ + return false; + } + objectId = mainCard.getId(); // for split cards if (affectedControllerId.equals(source.getControllerId()) && game.getState().getZone(objectId) == Zone.EXILED) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/g/GratefulApparition.java b/Mage.Sets/src/mage/cards/g/GratefulApparition.java index e62269bec7..5e96f199cb 100644 --- a/Mage.Sets/src/mage/cards/g/GratefulApparition.java +++ b/Mage.Sets/src/mage/cards/g/GratefulApparition.java @@ -26,7 +26,7 @@ public final class GratefulApparition extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // Whenever Grateful Apparition deals combat damage to a player or planeswalker, proliferate. + // Whenever Grateful Apparition deals combat damage to a player or planeswalker, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( new ProliferateEffect(), false ).setOrPlaneswalker(true)); diff --git a/Mage.Sets/src/mage/cards/g/GraveUpheaval.java b/Mage.Sets/src/mage/cards/g/GraveUpheaval.java index 9b2a0c33ec..94f5205e52 100644 --- a/Mage.Sets/src/mage/cards/g/GraveUpheaval.java +++ b/Mage.Sets/src/mage/cards/g/GraveUpheaval.java @@ -16,6 +16,7 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -33,7 +34,7 @@ public final class GraveUpheaval extends CardImpl { // Put target creature card from a graveyard onto the battlefield under your control. It gains haste. this.getSpellAbility().addEffect(new GraveUpheavalEffect()); - this.getSpellAbility().addTarget(new TargetCardInGraveyard()); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(new FilterCreatureCard())); // Basic landcycling {2} this.addAbility(new BasicLandcyclingAbility(new ManaCostsImpl("{2}"))); diff --git a/Mage.Sets/src/mage/cards/g/GreenwardenOfMurasa.java b/Mage.Sets/src/mage/cards/g/GreenwardenOfMurasa.java index 1c3c179b5b..3a2bdfcaaa 100644 --- a/Mage.Sets/src/mage/cards/g/GreenwardenOfMurasa.java +++ b/Mage.Sets/src/mage/cards/g/GreenwardenOfMurasa.java @@ -47,40 +47,4 @@ public final class GreenwardenOfMurasa extends CardImpl { public GreenwardenOfMurasa copy() { return new GreenwardenOfMurasa(this); } -} - -//class GreenwardenOfMurasaEffect extends OneShotEffect { -// -// public GreenwardenOfMurasaEffect() { -// super(Outcome.Benefit); -// this.staticText = "you may exile it. If you do, return target card from your graveyard to your hand"; -// } -// -// public GreenwardenOfMurasaEffect(final GreenwardenOfMurasaEffect effect) { -// super(effect); -// } -// -// @Override -// public GreenwardenOfMurasaEffect copy() { -// return new GreenwardenOfMurasaEffect(this); -// } -// -// @Override -// public boolean apply(Game game, Ability source) { -// Player controller = game.getPlayer(source.getControllerId()); -// MageObject sourceObject = game.getObject(source.getSourceId()); -// Card targetCard = game.getCard(getTargetPointer().getFirst(game, source)); -// if (controller != null && sourceObject != null && targetCard != null) { -// if (controller.chooseUse(outcome, "Exile " + sourceObject.getLogName() + " to return card from your graveyard to your hand?", source, game)) { -// // Setting the fixed target prevents to return Greenwarden of Murasa itself (becuase it's exiled meanwhile), -// // but of course you can target it as the ability triggers I guess -// Effect effect = new ReturnToHandTargetEffect(); -// effect.setTargetPointer(new FixedTarget(targetCard.getId(), targetCard.getZoneChangeCounter(game))); -// new ExileSourceEffect().apply(game, source); -// return effect.apply(game, source); -// } -// return true; -// } -// return false; -// } -//} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GrimAffliction.java b/Mage.Sets/src/mage/cards/g/GrimAffliction.java index e67c97ea19..38adaf2ac6 100644 --- a/Mage.Sets/src/mage/cards/g/GrimAffliction.java +++ b/Mage.Sets/src/mage/cards/g/GrimAffliction.java @@ -1,4 +1,3 @@ - package mage.cards.g; import mage.abilities.effects.common.counter.AddCountersTargetEffect; @@ -19,9 +18,10 @@ public final class GrimAffliction extends CardImpl { public GrimAffliction(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); - this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + // Put a -1/-1 counter on target creature, then proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.M1M1.createInstance())); this.getSpellAbility().addEffect(new ProliferateEffect().concatBy(", then")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } public GrimAffliction(final GrimAffliction card) { diff --git a/Mage.Sets/src/mage/cards/g/GrimReminder.java b/Mage.Sets/src/mage/cards/g/GrimReminder.java index 828cc12edd..b2c5af2216 100644 --- a/Mage.Sets/src/mage/cards/g/GrimReminder.java +++ b/Mage.Sets/src/mage/cards/g/GrimReminder.java @@ -87,7 +87,7 @@ class GrimReminderEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_NON_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { Cards cardsToReveal = new CardsImpl(card); diff --git a/Mage.Sets/src/mage/cards/g/GrinningTotem.java b/Mage.Sets/src/mage/cards/g/GrinningTotem.java index 326ad8c51c..041dc39da5 100644 --- a/Mage.Sets/src/mage/cards/g/GrinningTotem.java +++ b/Mage.Sets/src/mage/cards/g/GrinningTotem.java @@ -20,7 +20,6 @@ import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; -import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; @@ -82,7 +81,7 @@ class GrinningTotemSearchAndExileEffect extends OneShotEffect { if (you != null && targetOpponent != null && sourceObject != null) { if (targetOpponent.getLibrary().hasCards()) { TargetCardInLibrary targetCard = new TargetCardInLibrary(); - if (you.searchLibrary(targetCard, game, targetOpponent.getId())) { + if (you.searchLibrary(targetCard, source, game, targetOpponent.getId())) { Card card = targetOpponent.getLibrary().remove(targetCard.getFirstTarget(), game); if (card != null) { UUID exileZoneId = CardUtil.getCardExileZoneId(game, source); diff --git a/Mage.Sets/src/mage/cards/g/Grozoth.java b/Mage.Sets/src/mage/cards/g/Grozoth.java index e2d18a0432..b16f1a9844 100644 --- a/Mage.Sets/src/mage/cards/g/Grozoth.java +++ b/Mage.Sets/src/mage/cards/g/Grozoth.java @@ -88,7 +88,7 @@ class GrozothEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard != null && player != null && player.searchLibrary(target, game)) { + if (sourceCard != null && player != null && player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/g/GruesomeMenagerie.java b/Mage.Sets/src/mage/cards/g/GruesomeMenagerie.java index 6f90713868..b2ca5e6751 100644 --- a/Mage.Sets/src/mage/cards/g/GruesomeMenagerie.java +++ b/Mage.Sets/src/mage/cards/g/GruesomeMenagerie.java @@ -84,6 +84,7 @@ class GruesomeMenagerieEffect extends OneShotEffect { Cards cards = new CardsImpl(); Target target; target = new TargetCardInYourGraveyard(filter1); + target.setNotTarget(true); if (player.choose(outcome, target, source.getSourceId(), game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { @@ -91,6 +92,7 @@ class GruesomeMenagerieEffect extends OneShotEffect { } } target = new TargetCardInYourGraveyard(filter2); + target.setNotTarget(true); if (player.choose(outcome, target, source.getSourceId(), game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { @@ -98,6 +100,7 @@ class GruesomeMenagerieEffect extends OneShotEffect { } } target = new TargetCardInYourGraveyard(filter3); + target.setNotTarget(true); if (player.choose(outcome, target, source.getSourceId(), game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/g/GuidedStrike.java b/Mage.Sets/src/mage/cards/g/GuidedStrike.java index 486caf88f3..6df08e6956 100644 --- a/Mage.Sets/src/mage/cards/g/GuidedStrike.java +++ b/Mage.Sets/src/mage/cards/g/GuidedStrike.java @@ -1,4 +1,3 @@ - package mage.cards.g; import java.util.UUID; @@ -16,23 +15,25 @@ import mage.target.common.TargetCreaturePermanent; /** * * @author LoneFox - + * */ public final class GuidedStrike extends CardImpl { public GuidedStrike(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); // Target creature gets +1/+0 and gains first strike until end of turn. - this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); Effect effect = new BoostTargetEffect(1, 0, Duration.EndOfTurn); effect.setText("Target creature gets +1/+0"); this.getSpellAbility().addEffect(effect); effect = new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn); effect.setText("and gains first strike until end of turn"); this.getSpellAbility().addEffect(effect); + // Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + } public GuidedStrike(final GuidedStrike card) { diff --git a/Mage.Sets/src/mage/cards/g/GuildGlobe.java b/Mage.Sets/src/mage/cards/g/GuildGlobe.java new file mode 100644 index 0000000000..199b438439 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GuildGlobe.java @@ -0,0 +1,116 @@ +package mage.cards.g; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ManaEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GuildGlobe extends CardImpl { + + public GuildGlobe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // When Guild Globe enters the battlefield, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + + // {2}, {T}, Sacrifice Guild Globe: Add two mana of different colors. + Ability ability = new SimpleManaAbility( + Zone.BATTLEFIELD, new GuildGlobeManaEffect(), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private GuildGlobe(final GuildGlobe card) { + super(card); + } + + @Override + public GuildGlobe copy() { + return new GuildGlobe(this); + } +} + +class GuildGlobeManaEffect extends ManaEffect { + + GuildGlobeManaEffect() { + super(); + staticText = "Add two mana of different colors."; + } + + private GuildGlobeManaEffect(final GuildGlobeManaEffect effect) { + super(effect); + } + + @Override + public Mana produceMana(boolean netMana, Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return null; + } + + ChoiceColor color1 = new ChoiceColor(true, "Choose color 1"); + if (!player.choose(outcome, color1, game) || color1.getColor() == null) { + return null; + } + + ChoiceColor color2 = new ChoiceColor(true, "Choose color 2"); + color2.removeColorFromChoices(color1.getChoice()); + if (!player.choose(outcome, color2, game) || color2.getColor() == null) { + return null; + } + + if (color1.getColor().equals(color2.getColor())) { + game.informPlayers("Player " + player.getName() + " is cheating with mana choices."); + return null; + } + + Mana mana = new Mana(); + mana.add(color1.getMana(1)); + mana.add(color2.getMana(1)); + return mana; + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + checkToFirePossibleEvents(getMana(game, source), game, source); + player.getManaPool().addMana(getMana(game, source), game, source); + return true; + } + return false; + } + + @Override + public List getNetMana(Game game, Ability source) { + ArrayList netMana = new ArrayList<>(); + netMana.add(new Mana(0, 0, 0, 0, 0, 0, 2, 0)); + return netMana; + } + + @Override + public GuildGlobeManaEffect copy() { + return new GuildGlobeManaEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GuildpactInformant.java b/Mage.Sets/src/mage/cards/g/GuildpactInformant.java new file mode 100644 index 0000000000..564d26a637 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GuildpactInformant.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GuildpactInformant extends CardImpl { + + public GuildpactInformant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.FAERIE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Guildpact Informant deals combat damage to a player or planeswalker, + // proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new ProliferateEffect(), false + ).setOrPlaneswalker(true)); + } + + private GuildpactInformant(final GuildpactInformant card) { + super(card); + } + + @Override + public GuildpactInformant copy() { + return new GuildpactInformant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HardenedScales.java b/Mage.Sets/src/mage/cards/h/HardenedScales.java index ce764544f9..8d4390296f 100644 --- a/Mage.Sets/src/mage/cards/h/HardenedScales.java +++ b/Mage.Sets/src/mage/cards/h/HardenedScales.java @@ -1,7 +1,5 @@ - package mage.cards.h; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; @@ -17,14 +15,15 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class HardenedScales extends CardImpl { public HardenedScales(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); // 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. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new HardenedScalesEffect())); @@ -54,10 +53,7 @@ class HardenedScalesEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - int amount = event.getAmount(); - if (amount >= 1) { - event.setAmount(amount + 1); - } + event.setAmountForCounters(event.getAmount() + 1, true); return false; } @@ -68,15 +64,13 @@ class HardenedScalesEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getData().equals(CounterType.P1P1.getName())) { + if (event.getData().equals(CounterType.P1P1.getName()) && event.getAmount() > 0) { Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent == null) { permanent = game.getPermanentEntering(event.getTargetId()); } - if (permanent != null && permanent.isControlledBy(source.getControllerId()) - && permanent.isCreature()) { - return true; - } + return permanent != null && permanent.isControlledBy(source.getControllerId()) + && permanent.isCreature(); } return false; } diff --git a/Mage.Sets/src/mage/cards/h/HarvestSeason.java b/Mage.Sets/src/mage/cards/h/HarvestSeason.java index 02bbfdf85a..73e8e20a8f 100644 --- a/Mage.Sets/src/mage/cards/h/HarvestSeason.java +++ b/Mage.Sets/src/mage/cards/h/HarvestSeason.java @@ -13,7 +13,6 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -73,7 +72,7 @@ class HarvestSeasonEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, new PermanentsOnBattlefieldCount(filter).calculate(game, source, this), StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/h/HauntingEchoes.java b/Mage.Sets/src/mage/cards/h/HauntingEchoes.java index 5fc7b28ebd..c3e2e60bb4 100644 --- a/Mage.Sets/src/mage/cards/h/HauntingEchoes.java +++ b/Mage.Sets/src/mage/cards/h/HauntingEchoes.java @@ -69,7 +69,7 @@ class HauntingEchoesEffect extends OneShotEffect { int count = targetPlayer.getLibrary().count(filterCard, game); TargetCardInLibrary target = new TargetCardInLibrary(count, count, filterCard); - player.searchLibrary(target, game, targetPlayer.getId()); + player.searchLibrary(target, source, game, targetPlayer.getId()); List targets = target.getTargets(); for (UUID cardId : targets) { Card libraryCard = game.getCard(cardId); diff --git a/Mage.Sets/src/mage/cards/h/HeartwarmingRedemption.java b/Mage.Sets/src/mage/cards/h/HeartwarmingRedemption.java new file mode 100644 index 0000000000..80dd636e95 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeartwarmingRedemption.java @@ -0,0 +1,65 @@ +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.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HeartwarmingRedemption extends CardImpl { + + public HeartwarmingRedemption(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}{W}"); + + // Discard all the cards in your hand, then draw that many cards plus one. You gain life equal to the number of cards in your hand. + this.getSpellAbility().addEffect(new HeartwarmingRedemptionEffect()); + } + + private HeartwarmingRedemption(final HeartwarmingRedemption card) { + super(card); + } + + @Override + public HeartwarmingRedemption copy() { + return new HeartwarmingRedemption(this); + } +} + +class HeartwarmingRedemptionEffect extends OneShotEffect { + + HeartwarmingRedemptionEffect() { + super(Outcome.Benefit); + staticText = "Discard all the cards in your hand, then draw that many cards plus one. " + + "You gain life equal to the number of cards in your hand."; + } + + private HeartwarmingRedemptionEffect(final HeartwarmingRedemptionEffect effect) { + super(effect); + } + + @Override + public HeartwarmingRedemptionEffect copy() { + return new HeartwarmingRedemptionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int discarded = player.discard(player.getHand().size(), false, source, game).size(); + player.drawCards(discarded + 1, game); + player.gainLife(player.getHand().size(), game, source); + return true; + } +} +// Good night, sweet prince :( \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/h/HideSeek.java b/Mage.Sets/src/mage/cards/h/HideSeek.java index c39fa9f144..89ccbd290b 100644 --- a/Mage.Sets/src/mage/cards/h/HideSeek.java +++ b/Mage.Sets/src/mage/cards/h/HideSeek.java @@ -74,7 +74,7 @@ class SeekEffect extends OneShotEffect { if (player != null && opponent != null) { if (opponent.getLibrary().hasCards()) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (player.searchLibrary(target, game, opponent.getId())) { + if (player.searchLibrary(target, source, game, opponent.getId())) { UUID targetId = target.getFirstTarget(); Card card = opponent.getLibrary().remove(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/h/HiredGiant.java b/Mage.Sets/src/mage/cards/h/HiredGiant.java index c98dbcec74..ff87f5a7d4 100644 --- a/Mage.Sets/src/mage/cards/h/HiredGiant.java +++ b/Mage.Sets/src/mage/cards/h/HiredGiant.java @@ -72,7 +72,7 @@ class HiredGiantEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null && player.chooseUse(Outcome.PutCreatureInPlay, "Search your library for a land card and put it onto the battlefield?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(new FilterLandCard()); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card targetCard = player.getLibrary().getCard(target.getFirstTarget(), game); if (targetCard != null) { player.moveCards(targetCard, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/h/HoardingDragon.java b/Mage.Sets/src/mage/cards/h/HoardingDragon.java index 1d1e2ae51e..a5a5b394b6 100644 --- a/Mage.Sets/src/mage/cards/h/HoardingDragon.java +++ b/Mage.Sets/src/mage/cards/h/HoardingDragon.java @@ -79,7 +79,7 @@ class HoardingDragonEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(new FilterArtifactCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/h/HonorTheGodPharaoh.java b/Mage.Sets/src/mage/cards/h/HonorTheGodPharaoh.java index 1f8f365df0..654318e1ed 100644 --- a/Mage.Sets/src/mage/cards/h/HonorTheGodPharaoh.java +++ b/Mage.Sets/src/mage/cards/h/HonorTheGodPharaoh.java @@ -1,5 +1,6 @@ package mage.cards.h; +import java.util.UUID; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.keyword.AmassEffect; @@ -7,8 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import java.util.UUID; - /** * @author TheElk801 */ @@ -21,7 +20,7 @@ public final class HonorTheGodPharaoh extends CardImpl { this.getSpellAbility().addCost(new DiscardCardCost(false)); // Draw two cards. Amass 1. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).setText("draw two cards.")); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).setText("draw two cards")); this.getSpellAbility().addEffect(new AmassEffect(1)); } diff --git a/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java b/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java new file mode 100644 index 0000000000..f6e0b0a5a7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.ruleModifying.CombatDamageByToughnessEffect; +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 HuatliTheSunsHeart extends CardImpl { + + public HuatliTheSunsHeart(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G/W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUATLI); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(7)); + + // Each creature you control assigns combat damage equal to its toughness rather than its power. + this.addAbility(new SimpleStaticAbility(new CombatDamageByToughnessEffect( + StaticFilters.FILTER_PERMANENT_CREATURE, true + ))); + + // -3: You gain life equal to the greatest toughness among creatures you control. + this.addAbility(new LoyaltyAbility(new GainLifeEffect( + GreatestToughnessAmongControlledCreaturesValue.instance, + "You gain life equal to the greatest toughness among creatures you control" + ), -3)); + } + + private HuatliTheSunsHeart(final HuatliTheSunsHeart card) { + super(card); + } + + @Override + public HuatliTheSunsHeart copy() { + return new HuatliTheSunsHeart(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HuatliWarriorPoet.java b/Mage.Sets/src/mage/cards/h/HuatliWarriorPoet.java index 2cf27ec200..7d3ec36556 100644 --- a/Mage.Sets/src/mage/cards/h/HuatliWarriorPoet.java +++ b/Mage.Sets/src/mage/cards/h/HuatliWarriorPoet.java @@ -1,7 +1,5 @@ - package mage.cards.h; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.Mode; @@ -18,11 +16,7 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; 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.SuperType; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.DinosaurToken; @@ -30,8 +24,9 @@ import mage.target.Target; import mage.target.common.TargetCreaturePermanentAmount; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class HuatliWarriorPoet extends CardImpl { @@ -45,18 +40,21 @@ public final class HuatliWarriorPoet extends CardImpl { this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(3)); // +2: You gain life equal to the greatest power among creatures you control. - this.addAbility(new LoyaltyAbility(new GainLifeEffect(GreatestPowerAmongControlledCreaturesValue.instance, "You gain life equal to the greatest power among creatures you control"), 2)); + this.addAbility(new LoyaltyAbility(new GainLifeEffect( + GreatestPowerAmongControlledCreaturesValue.instance, + "You gain life equal to the greatest power among creatures you control" + ), 2)); // 0: Create a 3/3 green Dinosaur creature token with trample. this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DinosaurToken()), 0)); // -X: Huatli, Warrior Poet deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. - Ability ability = new LoyaltyAbility(new HuatliWarriorPoetDamageEffect(new HuatliXValue())); - ability.addTarget(new TargetCreaturePermanentAmount(new HuatliXValue())); + Ability ability = new LoyaltyAbility(new HuatliWarriorPoetDamageEffect(HuatliXValue.instance)); + ability.addTarget(new TargetCreaturePermanentAmount(HuatliXValue.instance)); this.addAbility(ability); } - public HuatliWarriorPoet(final HuatliWarriorPoet card) { + private HuatliWarriorPoet(final HuatliWarriorPoet card) { super(card); } @@ -66,9 +64,8 @@ public final class HuatliWarriorPoet extends CardImpl { } } -class HuatliXValue implements DynamicValue { - - private static final HuatliXValue defaultValue = new HuatliXValue(); +enum HuatliXValue implements DynamicValue { + instance; @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { @@ -82,7 +79,7 @@ class HuatliXValue implements DynamicValue { @Override public DynamicValue copy() { - return defaultValue; + return instance; } @Override @@ -96,7 +93,7 @@ class HuatliXValue implements DynamicValue { } public static HuatliXValue getDefault() { - return defaultValue; + return instance; } } @@ -104,12 +101,12 @@ class HuatliWarriorPoetDamageEffect extends OneShotEffect { protected DynamicValue amount; - public HuatliWarriorPoetDamageEffect(DynamicValue amount) { + HuatliWarriorPoetDamageEffect(DynamicValue amount) { super(Outcome.Damage); this.amount = amount; } - public HuatliWarriorPoetDamageEffect(final HuatliWarriorPoetDamageEffect effect) { + private HuatliWarriorPoetDamageEffect(final HuatliWarriorPoetDamageEffect effect) { super(effect); this.amount = effect.amount; } diff --git a/Mage.Sets/src/mage/cards/h/HuatlisRaptor.java b/Mage.Sets/src/mage/cards/h/HuatlisRaptor.java new file mode 100644 index 0000000000..886fccf0d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HuatlisRaptor.java @@ -0,0 +1,41 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HuatlisRaptor extends CardImpl { + + public HuatlisRaptor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}"); + + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Huatli's Raptor enters the battlefield, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) + this.addAbility(new EntersBattlefieldTriggeredAbility(new ProliferateEffect())); + } + + private HuatlisRaptor(final HuatlisRaptor card) { + super(card); + } + + @Override + public HuatlisRaptor copy() { + return new HuatlisRaptor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IdentityThief.java b/Mage.Sets/src/mage/cards/i/IdentityThief.java index 1ba731dba6..7003539065 100644 --- a/Mage.Sets/src/mage/cards/i/IdentityThief.java +++ b/Mage.Sets/src/mage/cards/i/IdentityThief.java @@ -1,7 +1,5 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -12,11 +10,7 @@ import mage.abilities.effects.common.CopyEffect; import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; 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.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.TokenPredicate; @@ -28,8 +22,9 @@ import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author spjspj */ public final class IdentityThief extends CardImpl { @@ -114,11 +109,7 @@ class IdentityThiefEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (controller != null && permanent != null && sourcePermanent != null) { - Permanent permanentReset = permanent.copy(); - permanentReset.getCounters(game).clear(); - permanentReset.getPower().resetToBaseValue(); - permanentReset.getToughness().resetToBaseValue(); - CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, permanentReset, source.getSourceId()); + CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, permanent, source.getSourceId()); if (controller.moveCardToExileWithInfo(permanent, source.getSourceId(), sourcePermanent.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true)) { // Copy exiled permanent game.addEffect(copyEffect, source); diff --git a/Mage.Sets/src/mage/cards/i/IgniteTheBeacon.java b/Mage.Sets/src/mage/cards/i/IgniteTheBeacon.java index 194db2e715..edd5644ff2 100644 --- a/Mage.Sets/src/mage/cards/i/IgniteTheBeacon.java +++ b/Mage.Sets/src/mage/cards/i/IgniteTheBeacon.java @@ -5,6 +5,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.common.FilterPlaneswalkerCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -14,7 +15,7 @@ import java.util.UUID; */ public final class IgniteTheBeacon extends CardImpl { - private static final FilterCard filter = new FilterCard("planeswalker cards"); + private static final FilterCard filter = new FilterPlaneswalkerCard("planeswalker cards"); public IgniteTheBeacon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{W}"); diff --git a/Mage.Sets/src/mage/cards/i/IlhargTheRazeBoar.java b/Mage.Sets/src/mage/cards/i/IlhargTheRazeBoar.java new file mode 100644 index 0000000000..b17883fd8a --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IlhargTheRazeBoar.java @@ -0,0 +1,103 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.GodEternalDiesTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +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.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IlhargTheRazeBoar extends CardImpl { + + public IlhargTheRazeBoar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.BOAR); + this.subtype.add(SubType.GOD); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Ilharg, the Raze-Boar attacks, you may put a creature card from your hand onto the battlefield tapped and attacking. Return that creature to your hand at the beginning of the next end step. + this.addAbility(new AttacksTriggeredAbility(new IlhargTheRazeBoarEffect(), true)); + + // When Ilharg, the Raze-Boar dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. + this.addAbility(new GodEternalDiesTriggeredAbility()); + } + + private IlhargTheRazeBoar(final IlhargTheRazeBoar card) { + super(card); + } + + @Override + public IlhargTheRazeBoar copy() { + return new IlhargTheRazeBoar(this); + } +} + +class IlhargTheRazeBoarEffect extends OneShotEffect { + + IlhargTheRazeBoarEffect() { + super(Outcome.Benefit); + staticText = "you may put a creature card from your hand onto the battlefield tapped and attacking. " + + "Return that creature to your hand at the beginning of the next end step."; + } + + private IlhargTheRazeBoarEffect(final IlhargTheRazeBoarEffect effect) { + super(effect); + } + + @Override + public IlhargTheRazeBoarEffect copy() { + return new IlhargTheRazeBoarEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_CREATURE); + if (!player.choose(outcome, player.getHand(), target, game)) { + return false; + } + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, true, null); + Permanent permanent = game.getPermanent(card.getId()); + if (permanent == null) { + return false; + } + game.getCombat().addAttackingCreature(permanent.getId(), game); + Effect effect = new ReturnToHandTargetEffect(); + effect.setText("return {this} to its owner's hand"); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InameDeathAspect.java b/Mage.Sets/src/mage/cards/i/InameDeathAspect.java index e2778335b8..7d9584467c 100644 --- a/Mage.Sets/src/mage/cards/i/InameDeathAspect.java +++ b/Mage.Sets/src/mage/cards/i/InameDeathAspect.java @@ -67,7 +67,7 @@ class InameDeathAspectEffect extends SearchEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null && player.searchLibrary(target, game)) { + if (player != null && player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()), Zone.GRAVEYARD, source, game); } diff --git a/Mage.Sets/src/mage/cards/i/Incoming.java b/Mage.Sets/src/mage/cards/i/Incoming.java index f20b1cd4da..49296f8deb 100644 --- a/Mage.Sets/src/mage/cards/i/Incoming.java +++ b/Mage.Sets/src/mage/cards/i/Incoming.java @@ -74,7 +74,7 @@ class IncomingEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java b/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java index f342eba3e9..7a9ccd4a2c 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java @@ -1,7 +1,6 @@ package mage.cards.i; -import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; @@ -75,7 +74,7 @@ class IncreasingAmbitionEffect extends SearchEffect { else { target = new TargetCardInLibrary(); } - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { for (UUID cardId: target.getTargets()) { Card card = player.getLibrary().remove(cardId, game); diff --git a/Mage.Sets/src/mage/cards/i/InexorableTide.java b/Mage.Sets/src/mage/cards/i/InexorableTide.java index 066b4b53d5..48bfa92cf5 100644 --- a/Mage.Sets/src/mage/cards/i/InexorableTide.java +++ b/Mage.Sets/src/mage/cards/i/InexorableTide.java @@ -1,28 +1,26 @@ - - package mage.cards.i; -import java.util.UUID; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import java.util.UUID; + /** - * * @author Loki, North */ public final class InexorableTide extends CardImpl { - public InexorableTide (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{U}{U}"); - + public InexorableTide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}{U}"); + // Whenever you cast a spell, proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.addAbility(new SpellCastControllerTriggeredAbility(new ProliferateEffect(), false)); } - public InexorableTide (final InexorableTide card) { + public InexorableTide(final InexorableTide card) { super(card); } diff --git a/Mage.Sets/src/mage/cards/i/InfernalSpawnOfEvil.java b/Mage.Sets/src/mage/cards/i/InfernalSpawnOfEvil.java new file mode 100644 index 0000000000..c346be1d1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InfernalSpawnOfEvil.java @@ -0,0 +1,104 @@ + +package mage.cards.i; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.condition.common.IsStepCondition; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.common.RevealSourceFromYourHandCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponentOrPlaneswalker; + +/** + * + * @author Ketsuban + */ +public final class InfernalSpawnOfEvil extends CardImpl { + + public InfernalSpawnOfEvil(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{6}{B}{B}{B}"); + this.subtype.add(SubType.BEAST); + + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // 1B, Reveal Infernal Spawn of Evil from your hand, Say "It's coming!": + // Infernal Spawn of Evil deals 1 damage to target opponent or planeswalker. + // Activate this ability only during your upkeep and only once each turn. + Ability ability = new LimitedTimesPerTurnActivatedAbility(Zone.HAND, new DamageTargetEffect(1), + new CompositeCost( + new ManaCostsImpl("{1}{B}"), + new CompositeCost( + new RevealSourceFromYourHandCost(), + new SayCost("It's coming!"), + "Reveal {this} from your hand, Say \"It's coming!\""), + "{1}{B}, Reveal {this} from your hand, Say \"It's coming!\""), + 1, new IsStepCondition(PhaseStep.UPKEEP, true)); + ability.addTarget(new TargetOpponentOrPlaneswalker()); + this.addAbility(ability); + } + + public InfernalSpawnOfEvil(final InfernalSpawnOfEvil card) { + super(card); + } + + @Override + public InfernalSpawnOfEvil copy() { + return new InfernalSpawnOfEvil(this); + } +} + +class SayCost extends CostImpl { + + private String message; + + public SayCost(String message) { + this.message = message; + } + + public SayCost(SayCost cost) { + super(cost); + this.message = cost.message; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + return true; + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + game.informPlayers(controller.getLogName() + ": " + message); + return true; + } + + @Override + public Cost copy() { + return new SayCost(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/Inhumaniac.java b/Mage.Sets/src/mage/cards/i/Inhumaniac.java index 8525bb3b53..e7ae91e038 100644 --- a/Mage.Sets/src/mage/cards/i/Inhumaniac.java +++ b/Mage.Sets/src/mage/cards/i/Inhumaniac.java @@ -1,7 +1,5 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -17,8 +15,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author spjspj */ public final class Inhumaniac extends CardImpl { @@ -71,7 +70,10 @@ class InhumaniacEffect extends OneShotEffect { } else if (amount >= 5) { permanent.addCounters(CounterType.P1P1.createInstance(2), source, game); } else if (amount == 1) { - permanent.getCounters(game).removeAllCounters(CounterType.P1P1); + int numToRemove = permanent.getCounters(game).getCount(CounterType.P1P1); + if (numToRemove > 0) { + permanent.removeCounters(CounterType.P1P1.getName(), numToRemove, game); + } } return true; } diff --git a/Mage.Sets/src/mage/cards/i/InsidiousDreams.java b/Mage.Sets/src/mage/cards/i/InsidiousDreams.java index b163be9b12..fecfde77a6 100644 --- a/Mage.Sets/src/mage/cards/i/InsidiousDreams.java +++ b/Mage.Sets/src/mage/cards/i/InsidiousDreams.java @@ -1,7 +1,6 @@ package mage.cards.i; -import java.util.List; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; @@ -78,7 +77,7 @@ class InsidiousDreamsEffect extends OneShotEffect { if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, amount, new FilterCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Cards chosen = new CardsImpl(); for (UUID cardId : target.getTargets()) { Card card = controller.getLibrary().remove(cardId, game); diff --git a/Mage.Sets/src/mage/cards/i/Intuition.java b/Mage.Sets/src/mage/cards/i/Intuition.java index 101f0ab1b9..35c8571b42 100644 --- a/Mage.Sets/src/mage/cards/i/Intuition.java +++ b/Mage.Sets/src/mage/cards/i/Intuition.java @@ -70,7 +70,7 @@ class IntuitionEffect extends SearchEffect { if (controller == null || opponent == null) return false; - if (controller.getLibrary().size() >= 3 && controller.searchLibrary(target, game)) { + if (controller.getLibrary().size() >= 3 && controller.searchLibrary(target, source, game)) { if (target.getTargets().size() == 3) { Cards cards = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/i/InvertInvent.java b/Mage.Sets/src/mage/cards/i/InvertInvent.java index fbeccdf652..787a79d070 100644 --- a/Mage.Sets/src/mage/cards/i/InvertInvent.java +++ b/Mage.Sets/src/mage/cards/i/InvertInvent.java @@ -114,14 +114,14 @@ class InventEffect extends OneShotEffect { } Cards cards = new CardsImpl(); TargetCardInLibrary target = new TargetCardInLibrary(filter1); - if (player.searchLibrary(target, game, false)) { + if (player.searchLibrary(target, source, game, false)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { cards.add(card); } } target = new TargetCardInLibrary(filter2); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { cards.add(card); diff --git a/Mage.Sets/src/mage/cards/i/IroncladKrovod.java b/Mage.Sets/src/mage/cards/i/IroncladKrovod.java new file mode 100644 index 0000000000..040cf4347c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IroncladKrovod.java @@ -0,0 +1,32 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IroncladKrovod extends CardImpl { + + public IroncladKrovod(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + } + + private IroncladKrovod(final IroncladKrovod card) { + super(card); + } + + @Override + public IroncladKrovod copy() { + return new IroncladKrovod(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaceArcaneStrategist.java b/Mage.Sets/src/mage/cards/j/JaceArcaneStrategist.java new file mode 100644 index 0000000000..bbaeeef62d --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JaceArcaneStrategist.java @@ -0,0 +1,108 @@ +package mage.cards.j; + +import mage.abilities.LoyaltyAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAllEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JaceArcaneStrategist extends CardImpl { + + public JaceArcaneStrategist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{U}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.JACE); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Whenever you draw your second card each turn, put a +1/+1 counter on target creature you control. + this.addAbility(new JaceArcaneStrategistTriggeredAbility()); + + // +1: Draw a card. + this.addAbility(new LoyaltyAbility(new DrawCardSourceControllerEffect(1), 1)); + + // -7: Creatures you control can't be blocked this turn. + this.addAbility(new LoyaltyAbility(new CantBeBlockedAllEffect( + StaticFilters.FILTER_CONTROLLED_CREATURE, Duration.EndOfTurn + ), -7)); + } + + private JaceArcaneStrategist(final JaceArcaneStrategist card) { + super(card); + } + + @Override + public JaceArcaneStrategist copy() { + return new JaceArcaneStrategist(this); + } +} + +class JaceArcaneStrategistTriggeredAbility extends TriggeredAbilityImpl { + + private boolean triggeredOnce = false; + private boolean triggeredTwice = false; + + JaceArcaneStrategistTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false); + this.addTarget(new TargetControlledCreaturePermanent()); + } + + private JaceArcaneStrategistTriggeredAbility(final JaceArcaneStrategistTriggeredAbility ability) { + super(ability); + this.triggeredOnce = ability.triggeredOnce; + this.triggeredTwice = ability.triggeredTwice; + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DREW_CARD + || event.getType() == GameEvent.EventType.END_PHASE_POST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.END_PHASE_POST) { + triggeredOnce = triggeredTwice = false; + return false; + } + if (event.getType() == GameEvent.EventType.DREW_CARD + && event.getPlayerId().equals(controllerId)) { + if (triggeredOnce) { + if (triggeredTwice) { + return false; + } else { + triggeredTwice = true; + return true; + } + } else { + triggeredOnce = true; + return false; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you draw your second card each turn, put a +1/+1 counter on target creature you control."; + } + + @Override + public JaceArcaneStrategistTriggeredAbility copy() { + return new JaceArcaneStrategistTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java index 8ef446b6da..f4030e7148 100644 --- a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java +++ b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java @@ -267,7 +267,7 @@ class JaceArchitectOfThoughtEffect3 extends OneShotEffect { playerName = "your"; } TargetCardInLibrary target = new TargetCardInLibrary(new FilterNonlandCard("nonland card from " + playerName + " library")); - if (controller.searchLibrary(target, game, playerId, !checkList.contains(playerId))) { + if (controller.searchLibrary(target, source, game, playerId, !checkList.contains(playerId))) { checkList.add(playerId); UUID targetId = target.getFirstTarget(); Card card = player.getLibrary().remove(targetId, game); diff --git a/Mage.Sets/src/mage/cards/j/JaceWielderOfMysteries.java b/Mage.Sets/src/mage/cards/j/JaceWielderOfMysteries.java index 4b05923f51..e6feb931d1 100644 --- a/Mage.Sets/src/mage/cards/j/JaceWielderOfMysteries.java +++ b/Mage.Sets/src/mage/cards/j/JaceWielderOfMysteries.java @@ -17,6 +17,7 @@ import mage.constants.SuperType; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; +import mage.target.TargetPlayer; import java.util.UUID; @@ -39,6 +40,7 @@ public final class JaceWielderOfMysteries extends CardImpl { // +1 Target player puts the top two cards of their library into their graveyard. Draw a card. Ability ability = new LoyaltyAbility(new PutLibraryIntoGraveTargetEffect(2), 1); + ability.addTarget(new TargetPlayer()); ability.addEffect(new DrawCardSourceControllerEffect(1)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/j/JacesProjection.java b/Mage.Sets/src/mage/cards/j/JacesProjection.java new file mode 100644 index 0000000000..e81b392100 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JacesProjection.java @@ -0,0 +1,57 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DrawCardControllerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.FilterPlaneswalkerPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JacesProjection extends CardImpl { + + private static final FilterPermanent filter = new FilterPlaneswalkerPermanent(SubType.JACE); + + public JacesProjection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + + this.subtype.add(SubType.WIZARD); + this.subtype.add(SubType.ILLUSION); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you draw a card, put a +1/+1 counter on Jace's Projection. + this.addAbility(new DrawCardControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false + )); + + // {3}{U}: Put a loyalty counter on target Jace planeswalker. + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.LOYALTY.createInstance()), new ManaCostsImpl("{3}{U}") + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private JacesProjection(final JacesProjection card) { + super(card); + } + + @Override + public JacesProjection copy() { + return new JacesProjection(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JacesRuse.java b/Mage.Sets/src/mage/cards/j/JacesRuse.java new file mode 100644 index 0000000000..42a1eea4b0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JacesRuse.java @@ -0,0 +1,42 @@ +package mage.cards.j; + +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryGraveyardPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JacesRuse extends CardImpl { + + private static final FilterCard filter = new FilterCard("Jace, Arcane Strategist"); + + static { + filter.add(new NamePredicate("Jace, Arcane Strategist")); + } + + public JacesRuse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}"); + + // Return up to two target creatures to their owner's hand. You may search your library and/or graveyard for a card named Jace, Arcane Strategist, reveal it, and put it into your hand. If you search your library this way, shuffle it. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addEffect(new SearchLibraryGraveyardPutInHandEffect(filter, false, true)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); + } + + private JacesRuse(final JacesRuse card) { + super(card); + } + + @Override + public JacesRuse copy() { + return new JacesRuse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JacesSentinel.java b/Mage.Sets/src/mage/cards/j/JacesSentinel.java index a60f5c14b2..da20a95e49 100644 --- a/Mage.Sets/src/mage/cards/j/JacesSentinel.java +++ b/Mage.Sets/src/mage/cards/j/JacesSentinel.java @@ -1,28 +1,24 @@ - package mage.cards.j; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; 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.filter.FilterPermanent; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.permanent.ControllerPredicate; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class JacesSentinel extends CardImpl { @@ -48,7 +44,7 @@ public final class JacesSentinel extends CardImpl { new BoostSourceEffect(1, 0, Duration.WhileOnBattlefield), new PermanentsOnTheBattlefieldCondition(filter), "As long as you control a Jace planeswalker, {this} gets +1/+0")); - ability.addEffect(new ConditionalContinuousEffect( + ability.addEffect(new ConditionalRestrictionEffect( new CantBeBlockedSourceEffect(), new PermanentsOnTheBattlefieldCondition(filter), "and can't be blocked")); diff --git a/Mage.Sets/src/mage/cards/j/JacesTriumph.java b/Mage.Sets/src/mage/cards/j/JacesTriumph.java new file mode 100644 index 0000000000..12ea23dd71 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JacesTriumph.java @@ -0,0 +1,42 @@ +package mage.cards.j; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +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.FilterPermanent; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JacesTriumph extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPlaneswalkerPermanent(SubType.JACE); + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + + public JacesTriumph(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Draw two cards. If you control a Jace planeswalker, draw three cards instead. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(3), new DrawCardSourceControllerEffect(2), + condition, "Draw two cards. If you control a Jace planeswalker, draw three cards instead." + )); + } + + private JacesTriumph(final JacesTriumph card) { + super(card); + } + + @Override + public JacesTriumph copy() { + return new JacesTriumph(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaradsOrders.java b/Mage.Sets/src/mage/cards/j/JaradsOrders.java index e1f9ff9a8b..bbee2a378b 100644 --- a/Mage.Sets/src/mage/cards/j/JaradsOrders.java +++ b/Mage.Sets/src/mage/cards/j/JaradsOrders.java @@ -1,7 +1,6 @@ package mage.cards.j; -import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -65,7 +64,7 @@ class JaradsOrdersEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 2, new FilterCreatureCard("creature cards")); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); for (UUID cardId: target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/j/JayaVeneratedFiremage.java b/Mage.Sets/src/mage/cards/j/JayaVeneratedFiremage.java new file mode 100644 index 0000000000..9a38e5d761 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JayaVeneratedFiremage.java @@ -0,0 +1,108 @@ +package mage.cards.j; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DamageTargetEffect; +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.target.common.TargetAnyTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JayaVeneratedFiremage extends CardImpl { + + public JayaVeneratedFiremage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.JAYA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // If another red source you control would deal damage to a permanent or player, it deals that much damage plus 1 to that permanent or player instead. + this.addAbility(new SimpleStaticAbility(new JayaVeneratedFiremageEffect())); + + // -2: Jaya, Venerated Firemage deals 2 damage tot any target. + Ability ability = new LoyaltyAbility(new DamageTargetEffect(2), -2); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private JayaVeneratedFiremage(final JayaVeneratedFiremage card) { + super(card); + } + + @Override + public JayaVeneratedFiremage copy() { + return new JayaVeneratedFiremage(this); + } +} + +class JayaVeneratedFiremageEffect extends ReplacementEffectImpl { + + JayaVeneratedFiremageEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "If another red source you control would deal damage to a permanent or player, " + + "it deals that much damage plus 1 to that permanent or player instead."; + } + + private JayaVeneratedFiremageEffect(final JayaVeneratedFiremageEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_CREATURE: + case DAMAGE_PLANESWALKER: + case DAMAGE_PLAYER: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (source.isControlledBy(game.getControllerId(event.getSourceId()))) { + MageObject sourceObject; + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (sourcePermanent == null) { + sourceObject = game.getObject(event.getSourceId()); + } else { + sourceObject = sourcePermanent; + } + return sourceObject != null && sourceObject.getColor(game).isRed() + && !sourceObject.getId().equals(source.getSourceId()); + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.addWithOverflowCheck(event.getAmount(), 1)); + return false; + } + + @Override + public JayaVeneratedFiremageEffect copy() { + return new JayaVeneratedFiremageEffect(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/j/JayasGreeting.java b/Mage.Sets/src/mage/cards/j/JayasGreeting.java new file mode 100644 index 0000000000..12cf5687a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JayasGreeting.java @@ -0,0 +1,34 @@ +package mage.cards.j; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JayasGreeting extends CardImpl { + + public JayasGreeting(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Jaya's Greeting deals 3 damage to target creature. Scry 1. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addEffect(new ScryEffect(1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private JayasGreeting(final JayasGreeting card) { + super(card); + } + + @Override + public JayasGreeting copy() { + return new JayasGreeting(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JestersCap.java b/Mage.Sets/src/mage/cards/j/JestersCap.java index b2637f6ffb..3a7c5223fb 100644 --- a/Mage.Sets/src/mage/cards/j/JestersCap.java +++ b/Mage.Sets/src/mage/cards/j/JestersCap.java @@ -70,7 +70,7 @@ class JestersCapEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && targetPlayer != null) { TargetCardInLibrary target = new TargetCardInLibrary(3, 3, new FilterCard()); - player.searchLibrary(target, game, targetPlayer.getId()); + player.searchLibrary(target, source, game, targetPlayer.getId()); for (UUID cardId : target.getTargets()) { final Card targetCard = game.getCard(cardId); if (targetCard != null) { diff --git a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java index 9b3d6d4fa1..7866dcfe79 100644 --- a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java +++ b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java @@ -99,7 +99,7 @@ class JourneyForTheElixirEffect extends OneShotEffect { if (!walkerFound || !landFound) { TargetCardInLibrary targetWalker = new TargetCardInLibrary(0, 1, filter); targetWalker.setNotTarget(true); - if (!walkerFound && player.searchLibrary(targetWalker, game, false)) { + if (!walkerFound && player.searchLibrary(targetWalker, source, game, false)) { Card card = game.getCard(targetWalker.getFirstTarget()); if (card != null) { cardsToHand.add(card); @@ -107,7 +107,7 @@ class JourneyForTheElixirEffect extends OneShotEffect { } TargetCardInLibrary targetLand = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_BASIC_LAND_A); targetLand.setNotTarget(true); - if (!landFound && player.searchLibrary(targetLand, game, false)) { + if (!landFound && player.searchLibrary(targetLand, source, game, false)) { Card card = game.getCard(targetLand.getFirstTarget()); if (card != null) { cardsToHand.add(card); diff --git a/Mage.Sets/src/mage/cards/j/JungleWayfinder.java b/Mage.Sets/src/mage/cards/j/JungleWayfinder.java index b459702934..48d123ceee 100644 --- a/Mage.Sets/src/mage/cards/j/JungleWayfinder.java +++ b/Mage.Sets/src/mage/cards/j/JungleWayfinder.java @@ -72,7 +72,7 @@ class JungleWayfinderEffect extends OneShotEffect { if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_BASIC_LAND); if (player.chooseUse(Outcome.Benefit, "Search your library for a card to put into your hand?", source, game)) { - player.searchLibrary(target, game); + player.searchLibrary(target, source, game); for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().getCard(cardId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/k/KahoMinamoHistorian.java b/Mage.Sets/src/mage/cards/k/KahoMinamoHistorian.java index 7dda8d0fb4..cc3c297d34 100644 --- a/Mage.Sets/src/mage/cards/k/KahoMinamoHistorian.java +++ b/Mage.Sets/src/mage/cards/k/KahoMinamoHistorian.java @@ -88,7 +88,7 @@ class KahoMinamoHistorianEffect extends SearchEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); if (controller != null && sourceObject != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { UUID exileZone = CardUtil.getCardExileZoneId(game, source); if (!target.getTargets().isEmpty()) { controller.moveCardsToExile(new CardsImpl(target.getTargets()).getCards(game), source, game, true, exileZone, sourceObject.getIdName()); diff --git a/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java b/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java new file mode 100644 index 0000000000..c4fdb5ccef --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java @@ -0,0 +1,146 @@ +package mage.cards.k; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.WishEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KarnTheGreatCreator extends CardImpl { + + private static final FilterPermanent filter + = new FilterArtifactPermanent("noncreature artifact"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.CREATURE))); + } + + public KarnTheGreatCreator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.KARN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Activated abilities of artifacts your opponents control can't be activated. + this.addAbility(new SimpleStaticAbility(new KarnTheGreatCreatorCantActivateEffect())); + + // +1: Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness equal to its converted mana cost. + Ability ability = new LoyaltyAbility(new KarnTheGreatCreatorAnimateEffect(), 1); + ability.addTarget(new TargetPermanent(0, 1, filter, false)); + this.addAbility(ability); + + // -2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand. + this.addAbility(new LoyaltyAbility(new WishEffect( + StaticFilters.FILTER_CARD_ARTIFACT_AN, true, true + ), -2)); + } + + private KarnTheGreatCreator(final KarnTheGreatCreator card) { + super(card); + } + + @Override + public KarnTheGreatCreator copy() { + return new KarnTheGreatCreator(this); + } +} + +class KarnTheGreatCreatorCantActivateEffect extends RestrictionEffect { + + KarnTheGreatCreatorCantActivateEffect() { + super(Duration.WhileOnBattlefield); + staticText = "Activated abilities of artifacts your opponents control can't be activated"; + } + + private KarnTheGreatCreatorCantActivateEffect(final KarnTheGreatCreatorCantActivateEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.isArtifact() && game.getOpponents(source.getControllerId()).contains(permanent.getControllerId()); + } + + @Override + public boolean canUseActivatedAbilities(Permanent permanent, Ability source, Game game, boolean canUseChooseDialogs) { + return false; + } + + @Override + public KarnTheGreatCreatorCantActivateEffect copy() { + return new KarnTheGreatCreatorCantActivateEffect(this); + } +} + +class KarnTheGreatCreatorAnimateEffect extends ContinuousEffectImpl { + + KarnTheGreatCreatorAnimateEffect() { + super(Duration.UntilYourNextTurn, Outcome.BecomeCreature); + staticText = "Until your next turn, up to one target noncreature artifact becomes " + + "an artifact creature with power and toughness each equal to its converted mana cost."; + } + + private KarnTheGreatCreatorAnimateEffect(final KarnTheGreatCreatorAnimateEffect effect) { + super(effect); + } + + @Override + public KarnTheGreatCreatorAnimateEffect copy() { + return new KarnTheGreatCreatorAnimateEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent artifact = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + if (artifact == null) { + return false; + } + switch (layer) { + case TypeChangingEffects_4: + if (sublayer == SubLayer.NA) { + if (!artifact.isCreature()) { + artifact.addCardType(CardType.CREATURE); + } + } + break; + + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + int cmc = artifact.getConvertedManaCost(); + artifact.getPower().setValue(cmc); + artifact.getToughness().setValue(cmc); + } + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.PTChangingEffects_7 || layer == Layer.TypeChangingEffects_4; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KarnsBastion.java b/Mage.Sets/src/mage/cards/k/KarnsBastion.java index c57110985f..4b5269cd16 100644 --- a/Mage.Sets/src/mage/cards/k/KarnsBastion.java +++ b/Mage.Sets/src/mage/cards/k/KarnsBastion.java @@ -23,7 +23,7 @@ public final class KarnsBastion extends CardImpl { // {T}: Add {C}. this.addAbility(new ColorlessManaAbility()); - // {4}, {T}: Proliferate. + // {4}, {T}: Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) Ability ability = new SimpleActivatedAbility(new ProliferateEffect(), new GenericManaCost(4)); ability.addCost(new TapSourceCost()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/k/KasminaEnigmaticMentor.java b/Mage.Sets/src/mage/cards/k/KasminaEnigmaticMentor.java new file mode 100644 index 0000000000..21db376b54 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KasminaEnigmaticMentor.java @@ -0,0 +1,101 @@ +package mage.cards.k; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.Mode; +import mage.abilities.SpellAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.WizardToken; +import mage.target.Target; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KasminaEnigmaticMentor extends CardImpl { + + public KasminaEnigmaticMentor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.KASMINA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Spells your opponents cast that target a creature or planeswalker you control cost {2} more to cast. + this.addAbility(new SimpleStaticAbility(new KasminaEnigmaticMentorCostReductionEffect())); + + // -2: Create a 2/2 blue Wizard creature token. Draw a card, then discard a card. + Ability ability = new LoyaltyAbility(new CreateTokenEffect(new WizardToken()), -2); + ability.addEffect(new DrawDiscardControllerEffect( + 1, 1 + ).setText("Draw a card, then discard a card.")); + this.addAbility(ability); + } + + private KasminaEnigmaticMentor(final KasminaEnigmaticMentor card) { + super(card); + } + + @Override + public KasminaEnigmaticMentor copy() { + return new KasminaEnigmaticMentor(this); + } +} + +class KasminaEnigmaticMentorCostReductionEffect extends CostModificationEffectImpl { + + KasminaEnigmaticMentorCostReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); + staticText = "Spells your opponents cast that target a creature or planeswalker you control cost {2} more to cast"; + } + + private KasminaEnigmaticMentorCostReductionEffect(KasminaEnigmaticMentorCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + SpellAbility spellAbility = (SpellAbility) abilityToModify; + CardUtil.adjustCost(spellAbility, -2); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify.getAbilityType() != AbilityType.SPELL + || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { + return false; + } + for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { + Mode mode = abilityToModify.getModes().get(modeId); + for (Target target : mode.getTargets()) { + for (UUID targetUUID : target.getTargets()) { + Permanent permanent = game.getPermanent(targetUUID); + if (permanent != null + && (permanent.isCreature() || permanent.isPlaneswalker()) + && permanent.isControlledBy(source.getControllerId())) { + return true; + } + } + } + } + return false; + } + + @Override + public KasminaEnigmaticMentorCostReductionEffect copy() { + return new KasminaEnigmaticMentorCostReductionEffect(this); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KasminasTransmutation.java b/Mage.Sets/src/mage/cards/k/KasminasTransmutation.java new file mode 100644 index 0000000000..5288591433 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KasminasTransmutation.java @@ -0,0 +1,73 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.permanent.token.TokenImpl; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KasminasTransmutation extends CardImpl { + + public KasminasTransmutation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature loses all abilities and has base power and toughness 1/1. + this.addAbility(new SimpleStaticAbility(new BecomesCreatureAttachedEffect( + new KasminasTransmutationToken(), "Enchanted creature loses all abilities " + + "and has base power and toughness 1/1", Duration.WhileOnBattlefield, + BecomesCreatureAttachedEffect.LoseType.ABILITIES + ))); + } + + private KasminasTransmutation(final KasminasTransmutation card) { + super(card); + } + + @Override + public KasminasTransmutation copy() { + return new KasminasTransmutation(this); + } +} + +class KasminasTransmutationToken extends TokenImpl { + + KasminasTransmutationToken() { + super("", "loses all abilities and has base power and toughness 1/1"); + cardType.add(CardType.CREATURE); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private KasminasTransmutationToken(final KasminasTransmutationToken token) { + super(token); + } + + public KasminasTransmutationToken copy() { + return new KasminasTransmutationToken(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/k/KayasGhostform.java b/Mage.Sets/src/mage/cards/k/KayasGhostform.java index 277d139bc4..84a9b05f39 100644 --- a/Mage.Sets/src/mage/cards/k/KayasGhostform.java +++ b/Mage.Sets/src/mage/cards/k/KayasGhostform.java @@ -86,6 +86,7 @@ class KayasGhostformTriggeredAbility extends TriggeredAbilityImpl { } if (zEvent.getTarget() != null && zEvent.getTarget().getAttachments() != null && zEvent.getTarget().getAttachments().contains(this.getSourceId())) { + getEffects().get(0).setValue("attachedTo", zEvent.getTarget()); return true; } else { // If both (attachment and attached went to graveyard at the same time, the attachemnets can be already removed from the attached object.) @@ -97,6 +98,7 @@ class KayasGhostformTriggeredAbility extends TriggeredAbilityImpl { Permanent attachedTo = game.getPermanentOrLKIBattlefield(attachment.getAttachedTo()); if (attachedTo != null && attachment.getAttachedToZoneChangeCounter() == attachedTo.getZoneChangeCounter(game)) { // zoneChangeCounter is stored in Permanent + getEffects().get(0).setValue("attachedTo", attachedTo); return true; } } diff --git a/Mage.Sets/src/mage/cards/k/KiorasDambreaker.java b/Mage.Sets/src/mage/cards/k/KiorasDambreaker.java index 8250c2bbe5..0dcf40c3cc 100644 --- a/Mage.Sets/src/mage/cards/k/KiorasDambreaker.java +++ b/Mage.Sets/src/mage/cards/k/KiorasDambreaker.java @@ -22,7 +22,7 @@ public final class KiorasDambreaker extends CardImpl { this.power = new MageInt(5); this.toughness = new MageInt(6); - // When Kiora's Dreammaker enters the battlefield, proliferate. + // When Kiora's Dreammaker enters the battlefield, proliferate. (Choose any number of permanents and/or players, then give each a counter of each kind already there.) this.addAbility(new EntersBattlefieldTriggeredAbility(new ProliferateEffect())); } diff --git a/Mage.Sets/src/mage/cards/k/KirtarsDesire.java b/Mage.Sets/src/mage/cards/k/KirtarsDesire.java index f42a3e25b1..9f987a41c7 100644 --- a/Mage.Sets/src/mage/cards/k/KirtarsDesire.java +++ b/Mage.Sets/src/mage/cards/k/KirtarsDesire.java @@ -1,28 +1,22 @@ - package mage.cards.k; -import java.util.UUID; -import mage.target.common.TargetCreaturePermanent; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CardsInControllerGraveCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.combat.CantAttackAttachedEffect; import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; -import mage.constants.Outcome; -import mage.target.TargetPermanent; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class KirtarsDesire extends CardImpl { @@ -43,8 +37,9 @@ public final class KirtarsDesire extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantAttackAttachedEffect(AttachmentType.AURA))); // Threshold - Enchanted creature can't block as long as seven or more cards are in your graveyard. - ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new CantAttackBlockAttachedEffect(AttachmentType.AURA), new CardsInControllerGraveCondition(7), + ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalRestrictionEffect( + new CantAttackBlockAttachedEffect(AttachmentType.AURA), + new CardsInControllerGraveCondition(7), "Enchanted creature can't block as long as seven or more cards are in your graveyard")); ability.setAbilityWord(AbilityWord.THRESHOLD); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/k/KjeldoranPride.java b/Mage.Sets/src/mage/cards/k/KjeldoranPride.java new file mode 100644 index 0000000000..d946ed77e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KjeldoranPride.java @@ -0,0 +1,64 @@ + +package mage.cards.k; + +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.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +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.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AnotherEnchantedPredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Ketsuban + */ +public final class KjeldoranPride extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature other than enchanted creature"); + + static { + filter.add(new AnotherEnchantedPredicate()); + } + + public KjeldoranPride(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.ENCHANTMENT }, "{1}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); + Ability enchantAbility = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(enchantAbility); + + // Enchanted creature gets +1/+2. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 2))); + + // 2U: Attach Kjeldoran Pride to target creature other than enchanted creature. + Ability ability = new SimpleActivatedAbility(new AttachEffect(Outcome.Benefit), new ManaCostsImpl<>("{2}{U}")); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + } + + public KjeldoranPride(final KjeldoranPride card) { + super(card); + } + + @Override + public KjeldoranPride copy() { + return new KjeldoranPride(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KnowledgeExploitation.java b/Mage.Sets/src/mage/cards/k/KnowledgeExploitation.java index 9c12f9d0ed..577861370b 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgeExploitation.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgeExploitation.java @@ -68,7 +68,7 @@ class KnowledgeExploitationEffect extends OneShotEffect { Player opponent = game.getPlayer(this.getTargetPointer().getFirst(game, source)); if (controller != null && opponent != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, new FilterInstantOrSorceryCard()); - if (controller.searchLibrary(target, game, opponent.getId())) { + if (controller.searchLibrary(target, source, game, opponent.getId())) { Card card = opponent.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { controller.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game)); diff --git a/Mage.Sets/src/mage/cards/k/KodamasReach.java b/Mage.Sets/src/mage/cards/k/KodamasReach.java index 762c9303b9..853fd0da29 100644 --- a/Mage.Sets/src/mage/cards/k/KodamasReach.java +++ b/Mage.Sets/src/mage/cards/k/KodamasReach.java @@ -68,7 +68,7 @@ class KodamasReachEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/k/KraulStinger.java b/Mage.Sets/src/mage/cards/k/KraulStinger.java new file mode 100644 index 0000000000..5b0e9299d3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KraulStinger.java @@ -0,0 +1,37 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KraulStinger extends CardImpl { + + public KraulStinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + } + + private KraulStinger(final KraulStinger card) { + super(card); + } + + @Override + public KraulStinger copy() { + return new KraulStinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KrenkoTinStreetKingpin.java b/Mage.Sets/src/mage/cards/k/KrenkoTinStreetKingpin.java index 8ef4d3f1f9..7ba8079a32 100644 --- a/Mage.Sets/src/mage/cards/k/KrenkoTinStreetKingpin.java +++ b/Mage.Sets/src/mage/cards/k/KrenkoTinStreetKingpin.java @@ -72,6 +72,6 @@ class KrenkoTinStreetKingpinEffect extends OneShotEffect { new AddCountersSourceEffect(CounterType.P1P1.createInstance()).apply(game, source); game.applyEffects(); int xValue = permanent.getPower().getValue(); - return new CreateTokenEffect(new GoblinToken(), xValue).apply(game, source); + return new CreateTokenEffect(new GoblinToken("WAR"), xValue).apply(game, source); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KronchWrangler.java b/Mage.Sets/src/mage/cards/k/KronchWrangler.java new file mode 100644 index 0000000000..32c97eae43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KronchWrangler.java @@ -0,0 +1,56 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +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.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KronchWrangler extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public KronchWrangler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever a creature with power 4 or greater enters the battlefield under your control, put a +1/+1 counter on Kronch Wrangler. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + } + + private KronchWrangler(final KronchWrangler card) { + super(card); + } + + @Override + public KronchWrangler copy() { + return new KronchWrangler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KyscuDrake.java b/Mage.Sets/src/mage/cards/k/KyscuDrake.java new file mode 100644 index 0000000000..709edbfb9a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyscuDrake.java @@ -0,0 +1,67 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author jmharmon + */ + +public final class KyscuDrake extends CardImpl { + + private static final FilterCard filter = new FilterCard("card named Viashivan Dragon"); + private static final FilterControlledCreaturePermanent filterSpitting = new FilterControlledCreaturePermanent("creature named Spitting Drake"); + static { + filter.add(new NamePredicate("Viashivan Dragon")); + filterSpitting.add(new NamePredicate("Spitting Drake")); + } + + public KyscuDrake(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + this.subtype.add(SubType.DRAKE); + + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // {G}: Kyscu Drake gets +0/+1 until end of turn. Activate this ability only once each turn. + this.addAbility(new LimitedTimesPerTurnActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(0, 1, Duration.EndOfTurn), new ManaCostsImpl("{G}"))); + + // Sacrifice Kyscu Drake and a creature named Spitting Drake: Search your library for a card named Viashivan Dragon and put that card onto the battlefield. Then shuffle your library. + TargetCardInLibrary target = new TargetCardInLibrary(1, 1, new FilterCard(filter)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SearchLibraryPutInPlayEffect(target,true,true, Outcome.PutCardInPlay), new SacrificeSourceCost()); + ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, filterSpitting, false))); + this.addAbility(ability); + + } + + public KyscuDrake(final KyscuDrake card) { + super(card); + } + + @Override + public KyscuDrake copy() { + return new KyscuDrake(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LawRuneEnforcer.java b/Mage.Sets/src/mage/cards/l/LawRuneEnforcer.java new file mode 100644 index 0000000000..1105305a9c --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LawRuneEnforcer.java @@ -0,0 +1,56 @@ +package mage.cards.l; + +import mage.MageInt; +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.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LawRuneEnforcer extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature with converted mana cost 2 or greater"); + + static { + filter.add(new ConvertedManaCostPredicate(ComparisonType.MORE_THAN, 1)); + } + + public LawRuneEnforcer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {1}, {T}: Tap target creature with converted mana cost 2 or greater. + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private LawRuneEnforcer(final LawRuneEnforcer card) { + super(card); + } + + @Override + public LawRuneEnforcer copy() { + return new LawRuneEnforcer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LazotepPlating.java b/Mage.Sets/src/mage/cards/l/LazotepPlating.java index a70cc8f565..71964e7e32 100644 --- a/Mage.Sets/src/mage/cards/l/LazotepPlating.java +++ b/Mage.Sets/src/mage/cards/l/LazotepPlating.java @@ -9,6 +9,8 @@ import mage.constants.CardType; import mage.constants.Duration; import java.util.UUID; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; /** * @author TheElk801 @@ -22,12 +24,16 @@ public final class LazotepPlating extends CardImpl { this.getSpellAbility().addEffect(new AmassEffect(1)); // You and permanents you control gain hexproof until end of turn. - this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + Effect effect = new GainAbilityControllerEffect( HexproofAbility.getInstance(), Duration.EndOfTurn - ).setText("
You and")); - this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + ); + Effect effect2 = new GainAbilityControlledEffect( HexproofAbility.getInstance(), Duration.EndOfTurn - )); + ); + effect.setText("You and permanents you control gain hexproof until end of turn."); + effect2.setText(""); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(effect2); } private LazotepPlating(final LazotepPlating card) { diff --git a/Mage.Sets/src/mage/cards/l/LegacyOfTheBeloved.java b/Mage.Sets/src/mage/cards/l/LegacyOfTheBeloved.java index 33e44bb526..512f24085c 100644 --- a/Mage.Sets/src/mage/cards/l/LegacyOfTheBeloved.java +++ b/Mage.Sets/src/mage/cards/l/LegacyOfTheBeloved.java @@ -77,7 +77,7 @@ class LegacyOfTheBelovedEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, p.getConvertedManaCost())); TargetCardInLibrary target = new TargetCardInLibrary(0, 2, filter); Player player = game.getPlayer(source.getControllerId()); - if (player != null && player.searchLibrary(target, game)) { + if (player != null && player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, false, false, false, null); player.shuffleLibrary(source, game); return true; diff --git a/Mage.Sets/src/mage/cards/l/LifesFinale.java b/Mage.Sets/src/mage/cards/l/LifesFinale.java index e0c208d7a2..5c8d83a138 100644 --- a/Mage.Sets/src/mage/cards/l/LifesFinale.java +++ b/Mage.Sets/src/mage/cards/l/LifesFinale.java @@ -70,7 +70,7 @@ class LifesFinaleEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && opponent != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 3, new FilterCreatureCard("creature cards from his library to put in his graveyard")); - if (player.searchLibrary(target, game, opponent.getId())) { + if (player.searchLibrary(target, source, game, opponent.getId())) { player.moveCards(new CardsImpl(target.getTargets()), Zone.GRAVEYARD, source, game); } opponent.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/l/LilianaDreadhordeGeneral.java b/Mage.Sets/src/mage/cards/l/LilianaDreadhordeGeneral.java index 269407c134..ba46e4fa66 100644 --- a/Mage.Sets/src/mage/cards/l/LilianaDreadhordeGeneral.java +++ b/Mage.Sets/src/mage/cards/l/LilianaDreadhordeGeneral.java @@ -90,7 +90,7 @@ class LilianaDreadhordeGeneralEffect extends OneShotEffect { keepFilter.add(new ControllerPredicate(TargetController.OPPONENT)); for (UUID opponentId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player opponent = game.getPlayer(opponentId); - if (opponent == null) { + if (opponent == null || !opponent.hasOpponent(source.getControllerId(), game)) { continue; } for (CardType cardType : CardType.values()) { diff --git a/Mage.Sets/src/mage/cards/l/LilianasTriumph.java b/Mage.Sets/src/mage/cards/l/LilianasTriumph.java index bdc39b8d95..5cb8a89ec4 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasTriumph.java +++ b/Mage.Sets/src/mage/cards/l/LilianasTriumph.java @@ -1,5 +1,6 @@ package mage.cards.l; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.dynamicvalue.common.StaticValue; @@ -21,7 +22,8 @@ import java.util.UUID; public final class LilianasTriumph extends CardImpl { private static final FilterControlledPlaneswalkerPermanent filter - = new FilterControlledPlaneswalkerPermanent(SubType.LILIANA, "a Liliana planeswalker"); + = new FilterControlledPlaneswalkerPermanent(SubType.LILIANA); + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); public LilianasTriumph(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); @@ -33,8 +35,7 @@ public final class LilianasTriumph extends CardImpl { this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new DiscardEachPlayerEffect( new StaticValue(1), false, TargetController.OPPONENT - ), new PermanentsOnTheBattlefieldCondition(filter), - "If you control a Liliana planeswalker, each opponent also discards a card." + ), condition, "If you control a Liliana planeswalker, each opponent also discards a card." )); } diff --git a/Mage.Sets/src/mage/cards/l/LinSivviDefiantHero.java b/Mage.Sets/src/mage/cards/l/LinSivviDefiantHero.java index 7335771d0a..c9ec6edc26 100644 --- a/Mage.Sets/src/mage/cards/l/LinSivviDefiantHero.java +++ b/Mage.Sets/src/mage/cards/l/LinSivviDefiantHero.java @@ -100,7 +100,7 @@ class LinSivviDefiantHeroEffect extends OneShotEffect { filter.add(new SubtypePredicate(SubType.REBEL)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/l/LivingTwister.java b/Mage.Sets/src/mage/cards/l/LivingTwister.java new file mode 100644 index 0000000000..11518351d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LivingTwister.java @@ -0,0 +1,64 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ReturnToHandChosenControlledPermanentEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LivingTwister extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledLandPermanent("a tapped land you control"); + + static { + filter.add(TappedPredicate.instance); + } + + public LivingTwister(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{R}{G}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // {1}{R}, Discard a land card: Living Twister deals 2 damage to any target. + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(2), new ManaCostsImpl("{1}{R}") + ); + ability.addCost(new DiscardTargetCost(new TargetCardInHand(StaticFilters.FILTER_CARD_LAND_A))); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // {G}: Return a tapped land you control to its owner's hand. + this.addAbility(new SimpleActivatedAbility( + new ReturnToHandChosenControlledPermanentEffect(filter), new ManaCostsImpl("{G}") + )); + } + + private LivingTwister(final LivingTwister card) { + super(card); + } + + @Override + public LivingTwister copy() { + return new LivingTwister(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/Lobotomy.java b/Mage.Sets/src/mage/cards/l/Lobotomy.java index 9471944381..c3decfd979 100644 --- a/Mage.Sets/src/mage/cards/l/Lobotomy.java +++ b/Mage.Sets/src/mage/cards/l/Lobotomy.java @@ -114,7 +114,7 @@ class LobotomyEffect extends OneShotEffect { // If the player has no nonland cards in their hand, you can still search that player's library and have him or her shuffle it. if (chosenCard != null || controller.chooseUse(outcome, "Search library anyway?", source, game)) { TargetCardInLibrary targetCardsLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCards); - controller.searchLibrary(targetCardsLibrary, game, targetPlayer.getId()); + controller.searchLibrary(targetCardsLibrary, source, game, targetPlayer.getId()); for (UUID cardId : targetCardsLibrary.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/l/LongTermPlans.java b/Mage.Sets/src/mage/cards/l/LongTermPlans.java index ee0b6f05c9..3f2b2b33e7 100644 --- a/Mage.Sets/src/mage/cards/l/LongTermPlans.java +++ b/Mage.Sets/src/mage/cards/l/LongTermPlans.java @@ -57,7 +57,7 @@ class LongTermPlansEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { player.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/m/ManaGeode.java b/Mage.Sets/src/mage/cards/m/ManaGeode.java new file mode 100644 index 0000000000..60edeadc52 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/ManaGeode.java @@ -0,0 +1,35 @@ +package mage.cards.m; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ManaGeode extends CardImpl { + + public ManaGeode(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // When Mana Geode enters the battlefield, scry 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1))); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private ManaGeode(final ManaGeode card) { + super(card); + } + + @Override + public ManaGeode copy() { + return new ManaGeode(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/ManaSeverance.java b/Mage.Sets/src/mage/cards/m/ManaSeverance.java index df991bad93..673b7f2e3a 100644 --- a/Mage.Sets/src/mage/cards/m/ManaSeverance.java +++ b/Mage.Sets/src/mage/cards/m/ManaSeverance.java @@ -64,7 +64,7 @@ class ManaSeveranceEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { Card card = controller.getLibrary().getCard(cardId, game); diff --git a/Mage.Sets/src/mage/cards/m/MangarasTome.java b/Mage.Sets/src/mage/cards/m/MangarasTome.java index c46179ffc4..78c7e296ef 100644 --- a/Mage.Sets/src/mage/cards/m/MangarasTome.java +++ b/Mage.Sets/src/mage/cards/m/MangarasTome.java @@ -73,7 +73,7 @@ class MangarasTomeSearchEffect extends OneShotEffect { Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (controller != null && permanent != null) { TargetCardInLibrary target = new TargetCardInLibrary(5, new FilterCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { for (UUID targetId : target.getTargets()) { Card card = controller.getLibrary().getCard(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/m/ManipulateFate.java b/Mage.Sets/src/mage/cards/m/ManipulateFate.java index 9603ec9d5d..72f87eca90 100644 --- a/Mage.Sets/src/mage/cards/m/ManipulateFate.java +++ b/Mage.Sets/src/mage/cards/m/ManipulateFate.java @@ -67,7 +67,7 @@ class ManipulateFateEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if(player != null) { - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { for (UUID targetId : getTargets()) { Card card = player.getLibrary().getCard(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/m/MaralenOfTheMornsong.java b/Mage.Sets/src/mage/cards/m/MaralenOfTheMornsong.java index f3f4a63f04..7167294fbf 100644 --- a/Mage.Sets/src/mage/cards/m/MaralenOfTheMornsong.java +++ b/Mage.Sets/src/mage/cards/m/MaralenOfTheMornsong.java @@ -100,7 +100,7 @@ class MaralenOfTheMornsongEffect2 extends OneShotEffect { if (player != null) { player.loseLife(3, game, false); TargetCardInLibrary target = new TargetCardInLibrary(); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()), Zone.HAND, source, game); } player.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/m/MartyrForTheCause.java b/Mage.Sets/src/mage/cards/m/MartyrForTheCause.java new file mode 100644 index 0000000000..19a17d1ec8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MartyrForTheCause.java @@ -0,0 +1,38 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MartyrForTheCause extends CardImpl { + + public MartyrForTheCause(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Martyr for the Cause dies, proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) + this.addAbility(new DiesTriggeredAbility(new ProliferateEffect())); + } + + private MartyrForTheCause(final MartyrForTheCause card) { + super(card); + } + + @Override + public MartyrForTheCause copy() { + return new MartyrForTheCause(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MartyrsOfKorlis.java b/Mage.Sets/src/mage/cards/m/MartyrsOfKorlis.java index bdd3efdb71..15edb14fd0 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrsOfKorlis.java +++ b/Mage.Sets/src/mage/cards/m/MartyrsOfKorlis.java @@ -1,40 +1,44 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceTappedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalReplacementEffect; +import mage.abilities.effects.Effect; import mage.abilities.effects.RedirectionEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.target.TargetPermanent; + +import java.util.UUID; /** - * * @author MarcoMarin */ public final class MartyrsOfKorlis extends CardImpl { public MartyrsOfKorlis(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); this.subtype.add(SubType.HUMAN); this.power = new MageInt(1); this.toughness = new MageInt(6); // As long as Martyrs of Korlis is untapped, all damage that would be dealt to you by artifacts is dealt to Martyrs of Korlis instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( + Effect effect = new ConditionalReplacementEffect( new RedirectArtifactDamageFromPlayerToSourceEffect(Duration.WhileOnBattlefield), new InvertCondition(SourceTappedCondition.instance), - "{this} redirects artifact damage from controller as long as it's untapped"))); + null); + effect.setText("{this} redirects artifact damage from controller as long as it's untapped"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); } public MartyrsOfKorlis(final MartyrsOfKorlis card) { @@ -50,7 +54,7 @@ public final class MartyrsOfKorlis extends CardImpl { class RedirectArtifactDamageFromPlayerToSourceEffect extends RedirectionEffect { public RedirectArtifactDamageFromPlayerToSourceEffect(Duration duration) { - super(duration); + super(duration); } public RedirectArtifactDamageFromPlayerToSourceEffect(final RedirectArtifactDamageFromPlayerToSourceEffect effect) { @@ -64,9 +68,13 @@ class RedirectArtifactDamageFromPlayerToSourceEffect extends RedirectionEffect { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getControllerId())&& + if (event.getTargetId().equals(source.getControllerId()) && game.getPermanentOrLKIBattlefield(event.getSourceId()).isArtifact()) { - this.redirectTarget.updateTarget(source.getSourceId(), game); + + TargetPermanent target = new TargetPermanent(); + target.add(source.getSourceId(), game); + this.redirectTarget = target; + return true; } return false; diff --git a/Mage.Sets/src/mage/cards/m/MassacreGirl.java b/Mage.Sets/src/mage/cards/m/MassacreGirl.java index 239068f35b..593f96b51d 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreGirl.java +++ b/Mage.Sets/src/mage/cards/m/MassacreGirl.java @@ -68,7 +68,7 @@ class MassacreGirlEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { game.addEffect(new BoostAllEffect(-1, -1, Duration.EndOfTurn, true), source); - game.addDelayedTriggeredAbility(new MassacreGirlDelayedTriggeredAbility()); + game.addDelayedTriggeredAbility(new MassacreGirlDelayedTriggeredAbility(), source); return true; } } diff --git a/Mage.Sets/src/mage/cards/m/MeliraSylvokOutcast.java b/Mage.Sets/src/mage/cards/m/MeliraSylvokOutcast.java index d3d868a2e2..3c6124c946 100644 --- a/Mage.Sets/src/mage/cards/m/MeliraSylvokOutcast.java +++ b/Mage.Sets/src/mage/cards/m/MeliraSylvokOutcast.java @@ -1,8 +1,5 @@ - package mage.cards.m; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -19,14 +16,16 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import java.util.Set; +import java.util.UUID; + /** - * * @author BetaSteward */ public final class MeliraSylvokOutcast extends CardImpl { public MeliraSylvokOutcast(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.SCOUT); @@ -78,7 +77,7 @@ class MeliraSylvokOutcastEffect extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.ADD_COUNTER; + return event.getType() == EventType.ADD_COUNTERS; } @Override @@ -111,7 +110,7 @@ class MeliraSylvokOutcastEffect2 extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.ADD_COUNTER; + return event.getType() == EventType.ADD_COUNTERS; } @Override @@ -121,13 +120,10 @@ class MeliraSylvokOutcastEffect2 extends ReplacementEffectImpl { if (perm == null) { perm = game.getPermanentEntering(event.getTargetId()); } - if (perm != null && perm.isCreature() && perm.isControlledBy(source.getControllerId())) { - return true; - } + return perm != null && perm.isCreature() && perm.isControlledBy(source.getControllerId()); } return false; } - } class MeliraSylvokOutcastEffect3 extends ContinuousEffectImpl { diff --git a/Mage.Sets/src/mage/cards/m/MerfolkSkydiver.java b/Mage.Sets/src/mage/cards/m/MerfolkSkydiver.java index 8f4b86dc5d..cd2952e5a4 100644 --- a/Mage.Sets/src/mage/cards/m/MerfolkSkydiver.java +++ b/Mage.Sets/src/mage/cards/m/MerfolkSkydiver.java @@ -40,7 +40,7 @@ public final class MerfolkSkydiver extends CardImpl { ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); - // {3}{G}{U}: Proliferate. + // {3}{G}{U}: Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) this.addAbility(new SimpleActivatedAbility(new ProliferateEffect(), new ManaCostsImpl("{3}{G}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/m/MeteorBlast.java b/Mage.Sets/src/mage/cards/m/MeteorBlast.java index 9331bce50d..c4d2ae4233 100644 --- a/Mage.Sets/src/mage/cards/m/MeteorBlast.java +++ b/Mage.Sets/src/mage/cards/m/MeteorBlast.java @@ -1,21 +1,17 @@ package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; 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.players.Player; -import mage.target.Target; import mage.target.common.TargetAnyTarget; import mage.target.targetadjustment.TargetAdjuster; +import java.util.UUID; + /** - * * @author fireshoes */ public final class MeteorBlast extends CardImpl { @@ -24,10 +20,13 @@ public final class MeteorBlast extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}{R}"); // Meteor Blast deals 4 damage to each of X target creatures and/or players. - this.getSpellAbility().addEffect(new MeteorBlastEffect()); + this.getSpellAbility().addEffect( + new DamageTargetEffect(4).setText("{this} deals 4 damage to each of X targets") + ); + this.getSpellAbility().setTargetAdjuster(MeteorBlastAdjuster.instance); } - public MeteorBlast(final MeteorBlast card) { + private MeteorBlast(final MeteorBlast card) { super(card); } @@ -44,47 +43,7 @@ enum MeteorBlastAdjuster implements TargetAdjuster { public void adjustTargets(Ability ability, Game game) { int xValue = ability.getManaCostsToPay().getX(); if (xValue > 0) { - Target target = new TargetAnyTarget(xValue); - ability.addTarget(target); + ability.addTarget(new TargetAnyTarget(xValue)); } } } - -class MeteorBlastEffect extends OneShotEffect { - - public MeteorBlastEffect() { - super(Outcome.Damage); - staticText = "{this} deals 4 damage to each of X target creatures and/or players"; - } - - public MeteorBlastEffect(final MeteorBlastEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (!source.getTargets().isEmpty()) { - for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { - Permanent creature = game.getPermanent(targetId); - if (creature != null) { - creature.damage(4, source.getSourceId(), game, false, true); - } else { - Player player = game.getPlayer(targetId); - if (player != null) { - player.damage(4, source.getSourceId(), game, false, true); - } - } - } - } - return true; - } - return false; - } - - @Override - public MeteorBlastEffect copy() { - return new MeteorBlastEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/m/Mimeofacture.java b/Mage.Sets/src/mage/cards/m/Mimeofacture.java index 8f4237b38c..e83670c3a9 100644 --- a/Mage.Sets/src/mage/cards/m/Mimeofacture.java +++ b/Mage.Sets/src/mage/cards/m/Mimeofacture.java @@ -79,7 +79,7 @@ class MimeofactureEffect extends OneShotEffect { FilterCard filter = new FilterCard("card named " + permanent.getName()); filter.add(new NamePredicate(permanent.getName())); TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); - if (controller.searchLibrary(target, game, opponent.getId())) { + if (controller.searchLibrary(target, source, game, opponent.getId())) { Card card = opponent.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/m/MindsDesire.java b/Mage.Sets/src/mage/cards/m/MindsDesire.java index 49f6b5ac8d..b270cd023c 100644 --- a/Mage.Sets/src/mage/cards/m/MindsDesire.java +++ b/Mage.Sets/src/mage/cards/m/MindsDesire.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.ContinuousEffect; @@ -19,8 +17,9 @@ import mage.players.Player; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author emerald000 */ public final class MindsDesire extends CardImpl { @@ -68,11 +67,14 @@ class MindsDesireEffect extends OneShotEffect { controller.shuffleLibrary(source, game); Card card = controller.getLibrary().getFromTop(game); if (card != null) { - UUID exileId = UUID.randomUUID(); - controller.moveCardsToExile(card, source, game, true, exileId, CardUtil.createObjectRealtedWindowTitle(source, game, null)); - ContinuousEffect effect = new MindsDesireCastFromExileEffect(); - effect.setTargetPointer(new FixedTargets(game.getExile().getExileZone(exileId).getCards(game), game)); - game.addEffect(effect, source); + UUID exileId = CardUtil.getExileZoneId(controller.getId().toString() + "-" + game.getState().getTurnNum() + "-" + MindsDesire.class.toString(), game); + String exileName = "Mind's Desire free cast on " + game.getState().getTurnNum() + " turn for " + controller.getName(); + game.getExile().createZone(exileId, exileName).setCleanupOnEndTurn(true); + if (controller.moveCardsToExile(card, source, game, true, exileId, exileName)) { + ContinuousEffect effect = new MindsDesireCastFromExileEffect(); + effect.setTargetPointer(new FixedTargets(game.getExile().getExileZone(exileId).getCards(game), game)); + game.addEffect(effect, source); + } } return true; } diff --git a/Mage.Sets/src/mage/cards/m/MineLayer.java b/Mage.Sets/src/mage/cards/m/MineLayer.java index 113cb6f691..1932c318ed 100644 --- a/Mage.Sets/src/mage/cards/m/MineLayer.java +++ b/Mage.Sets/src/mage/cards/m/MineLayer.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BecomesTappedTriggeredAbility; @@ -12,11 +10,11 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; -import mage.constants.SubType; 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.counters.CounterType; import mage.filter.common.FilterLandPermanent; @@ -25,8 +23,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetLandPermanent; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class MineLayer extends CardImpl { @@ -87,7 +86,10 @@ class RemoveAllMineCountersEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(CardType.LAND)) { if (permanent != null) { - permanent.getCounters(game).removeAllCounters(CounterType.MINE); + int numToRemove = permanent.getCounters(game).getCount(CounterType.MINE); + if (numToRemove > 0) { + permanent.removeCounters(CounterType.MINE.getName(), numToRemove, game); + } } } return true; diff --git a/Mage.Sets/src/mage/cards/m/MishraArtificerProdigy.java b/Mage.Sets/src/mage/cards/m/MishraArtificerProdigy.java index a7e9e69d51..a801c5a83d 100644 --- a/Mage.Sets/src/mage/cards/m/MishraArtificerProdigy.java +++ b/Mage.Sets/src/mage/cards/m/MishraArtificerProdigy.java @@ -135,7 +135,7 @@ class MishraArtificerProdigyEffect extends OneShotEffect { // Library if (card == null && controller.chooseUse(Outcome.Neutral, "Search your library?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { card = game.getCard(target.getFirstTarget()); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/m/MissDemeanor.java b/Mage.Sets/src/mage/cards/m/MissDemeanor.java new file mode 100644 index 0000000000..e262d64ff8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MissDemeanor.java @@ -0,0 +1,87 @@ + +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +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.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author Ketsuban + */ +public final class MissDemeanor extends CardImpl { + + public MissDemeanor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.LADYOFPROPERETIQUETTE); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // At the beginning of each other player's upkeep, you may compliment that player on their game play. If you don't, sacrifice Miss Demeanour. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new MissDemeanorEffect(), TargetController.NOT_YOU, false, true)); + } + + public MissDemeanor(final MissDemeanor card) { + super(card); + } + + @Override + public MissDemeanor copy() { + return new MissDemeanor(this); + } +} + +class MissDemeanorEffect extends OneShotEffect { + + public MissDemeanorEffect() { + super(Outcome.Sacrifice); + this.staticText = "you may compliment that player on their game play. If you don't, sacrifice {this}"; + } + + public MissDemeanorEffect(final MissDemeanorEffect effect) { + super(effect); + } + + @Override + public MissDemeanorEffect copy() { + return new MissDemeanorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourceObject = (Permanent) source.getSourceObjectIfItStillExists(game); + String activePlayerName = game.getPlayer(game.getActivePlayerId()).getName(); + if (sourceObject != null) { + if (controller.chooseUse(outcome, "Compliment " + activePlayerName + " on their game play?", source, game)) { + // TODO(Ketsuban): this could probably stand to be randomly chosen from a pool of compliments + game.informPlayers(controller.getLogName() + ": That's a well-built deck and you pilot it well, " + activePlayerName + "."); + } else { + sourceObject.sacrifice(source.getSourceId(), game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java b/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java new file mode 100644 index 0000000000..35a6304eed --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java @@ -0,0 +1,126 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.token.TokenImpl; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MobilizedDistrict extends CardImpl { + + private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent(); + + static { + filter.add(new SupertypePredicate(SuperType.LEGENDARY)); + filter.add(new ControllerPredicate(TargetController.YOU)); + } + + static final DynamicValue cardsCount = new PermanentsOnBattlefieldCount(filter); + + public MobilizedDistrict(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {4}: Mobilized District becomes a 3/3 Citizen creature with vigilance until end of turn. It's still a land. This ability costs {1} less to activate for each legendary creature and planeswalker you control. + // TODO: Make ability properly copiable + Ability ability = new SimpleActivatedAbility(new BecomesCreatureSourceEffect( + new MobilizedDistrictToken(), "land", Duration.EndOfTurn + ).setText("{this} becomes a 3/3 Citizen creature with vigilance until end of turn. " + + "It's still a land. This ability costs {1} less to activate " + + "for each legendary creature and planeswalker you control." + ), new GenericManaCost(4)); + this.addAbility(ability); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new MobilizedDistrictCostIncreasingEffect(ability.getOriginalId()) + ).addHint(new ValueHint("Legendary creatures and planeswalkers you control", cardsCount))); + } + + private MobilizedDistrict(final MobilizedDistrict card) { + super(card); + } + + @Override + public MobilizedDistrict copy() { + return new MobilizedDistrict(this); + } +} + +class MobilizedDistrictToken extends TokenImpl { + + MobilizedDistrictToken() { + super("", "3/3 Citizen creature with vigilance"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.CITIZEN); + power = new MageInt(3); + toughness = new MageInt(3); + addAbility(VigilanceAbility.getInstance()); + } + + private MobilizedDistrictToken(final MobilizedDistrictToken token) { + super(token); + } + + public MobilizedDistrictToken copy() { + return new MobilizedDistrictToken(this); + } +} + +class MobilizedDistrictCostIncreasingEffect extends CostModificationEffectImpl { + + private final UUID originalId; + + MobilizedDistrictCostIncreasingEffect(UUID originalId) { + super(Duration.EndOfGame, Outcome.Benefit, CostModificationType.REDUCE_COST); + this.originalId = originalId; + } + + private MobilizedDistrictCostIncreasingEffect(final MobilizedDistrictCostIncreasingEffect effect) { + super(effect); + this.originalId = effect.originalId; + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int count = MobilizedDistrict.cardsCount.calculate(game, source, this); + CardUtil.reduceCost(abilityToModify, count); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify.getOriginalId().equals(originalId); + } + + @Override + public MobilizedDistrictCostIncreasingEffect copy() { + return new MobilizedDistrictCostIncreasingEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoltenVortex.java b/Mage.Sets/src/mage/cards/m/MoltenVortex.java index e436857f45..19474d57f0 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenVortex.java +++ b/Mage.Sets/src/mage/cards/m/MoltenVortex.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.DiscardTargetCost; @@ -10,23 +9,25 @@ import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterLandCard; -import mage.target.common.TargetCardInHand; +import mage.filter.StaticFilters; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; /** - * * @author fireshoes */ public final class MoltenVortex extends CardImpl { public MoltenVortex(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); // {R}, Discard a land card: Molten Vortex deals 2 damage to any target. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(2), new DiscardTargetCost(new TargetCardInHand(new FilterLandCard()))); - ability.addCost(new ManaCostsImpl("{R}")); + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(2), new ManaCostsImpl("{R}") + ); + ability.addCost(new DiscardTargetCost(new TargetCardInHand(StaticFilters.FILTER_CARD_LAND_A))); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MowuLoyalCompanion.java b/Mage.Sets/src/mage/cards/m/MowuLoyalCompanion.java index 6b3fdc2c7c..946b515178 100644 --- a/Mage.Sets/src/mage/cards/m/MowuLoyalCompanion.java +++ b/Mage.Sets/src/mage/cards/m/MowuLoyalCompanion.java @@ -63,10 +63,7 @@ class MowuLoyalCompanionEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - int amount = event.getAmount(); - if (amount > 0) { - event.setAmount(amount + 1); - } + event.setAmountForCounters(event.getAmount() + 1, true); return false; } @@ -77,15 +74,13 @@ class MowuLoyalCompanionEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getData().equals(CounterType.P1P1.getName())) { + if (event.getData().equals(CounterType.P1P1.getName()) && event.getAmount() > 0) { Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent == null) { permanent = game.getPermanentEntering(event.getTargetId()); } - if (permanent != null && permanent.getId().equals(source.getSourceId()) - && permanent.isCreature()) { - return true; - } + return permanent != null && permanent.getId().equals(source.getSourceId()) + && permanent.isCreature(); } return false; } diff --git a/Mage.Sets/src/mage/cards/m/MyrIncubator.java b/Mage.Sets/src/mage/cards/m/MyrIncubator.java index 916a4f0e9d..a8996c7b95 100644 --- a/Mage.Sets/src/mage/cards/m/MyrIncubator.java +++ b/Mage.Sets/src/mage/cards/m/MyrIncubator.java @@ -72,7 +72,7 @@ class MyrIncubatorEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null - && controller.searchLibrary(target, game)) { + && controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { tokensToCreate = target.getTargets().size(); controller.moveCards(new CardsImpl(target.getTargets()), Zone.EXILED, source, game); diff --git a/Mage.Sets/src/mage/cards/n/NahiriStormOfStone.java b/Mage.Sets/src/mage/cards/n/NahiriStormOfStone.java index b7e67ca344..133cb36878 100644 --- a/Mage.Sets/src/mage/cards/n/NahiriStormOfStone.java +++ b/Mage.Sets/src/mage/cards/n/NahiriStormOfStone.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.costs.Cost; import mage.abilities.costs.common.PayVariableLoyaltyCost; import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalCostModificationEffect; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; @@ -56,7 +57,7 @@ public final class NahiriStormOfStone extends CardImpl { ), MyTurnCondition.instance, "As long as it's your turn, " + "creatures you control have first strike" )); - ability.addEffect(new ConditionalContinuousEffect( + ability.addEffect(new ConditionalCostModificationEffect( new AbilitiesCostReductionControllerEffect( EquipAbility.class, "Equip" ), MyTurnCondition.instance, "and equip abilities you activate cost {1} less to activate" @@ -98,7 +99,12 @@ enum NahiriStormOfStoneValue implements DynamicValue { } @Override - public String getMessage() { + public String toString() { return "X"; } + + @Override + public String getMessage() { + return ""; + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NahiriTheHarbinger.java b/Mage.Sets/src/mage/cards/n/NahiriTheHarbinger.java index 2e0fba2b4c..693c492a3c 100644 --- a/Mage.Sets/src/mage/cards/n/NahiriTheHarbinger.java +++ b/Mage.Sets/src/mage/cards/n/NahiriTheHarbinger.java @@ -110,7 +110,7 @@ class NahiriTheHarbingerEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/n/NarsetParterOfVeils.java b/Mage.Sets/src/mage/cards/n/NarsetParterOfVeils.java new file mode 100644 index 0000000000..9a6571e6ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NarsetParterOfVeils.java @@ -0,0 +1,94 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.watchers.common.CardsAmountDrawnThisTurnWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NarsetParterOfVeils extends CardImpl { + + private static final FilterCard filter = new FilterCard("noncreature, nonland card"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.CREATURE))); + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public NarsetParterOfVeils(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.NARSET); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Each opponent can't draw more than one card each turn. + this.addAbility(new SimpleStaticAbility(new NarsetParterOfVeilsEffect()), new CardsAmountDrawnThisTurnWatcher()); + + // -2: Look at the top four cards of your library. You may reveal a noncreature, nonland card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new LoyaltyAbility(new LookLibraryAndPickControllerEffect( + new StaticValue(4), false, new StaticValue(1), filter, + Zone.LIBRARY, false, true, false, Zone.HAND, + true, false, false + ).setBackInRandomOrder(true).setText("Look at the top four cards of your library. " + + "You may reveal a noncreature, nonland card from among them and put it into your hand. " + + "Put the rest on the bottom of your library in a random order." + ), -2)); + } + + private NarsetParterOfVeils(final NarsetParterOfVeils card) { + super(card); + } + + @Override + public NarsetParterOfVeils copy() { + return new NarsetParterOfVeils(this); + } +} + +class NarsetParterOfVeilsEffect extends ContinuousRuleModifyingEffectImpl { + + NarsetParterOfVeilsEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment, false, false); + staticText = "Each opponent can't draw more than one card each turn"; + } + + private NarsetParterOfVeilsEffect(final NarsetParterOfVeilsEffect effect) { + super(effect); + } + + @Override + public NarsetParterOfVeilsEffect copy() { + return new NarsetParterOfVeilsEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DRAW_CARD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + CardsAmountDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsAmountDrawnThisTurnWatcher.class); + Player controller = game.getPlayer(source.getControllerId()); + return watcher != null && controller != null && watcher.getAmountCardsDrawn(event.getPlayerId()) >= 1 + && game.isOpponent(controller, event.getPlayerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NarsetsReversal.java b/Mage.Sets/src/mage/cards/n/NarsetsReversal.java new file mode 100644 index 0000000000..a676babeac --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NarsetsReversal.java @@ -0,0 +1,37 @@ +package mage.cards.n; + +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NarsetsReversal extends CardImpl { + + public NarsetsReversal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{U}"); + + // Copy target instant or sorcery spell, then return it to its owner's hand. You may choose new targets for the copy. + this.getSpellAbility().addEffect(new CopyTargetSpellEffect() + .setText("Copy target instant or sorcery spell,")); + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect() + .setText("then return it to its owner's hand. You may choose new targets for the copy.")); + this.getSpellAbility().addTarget(new TargetSpell(StaticFilters.FILTER_SPELL_INSTANT_OR_SORCERY)); + } + + private NarsetsReversal(final NarsetsReversal card) { + super(card); + } + + @Override + public NarsetsReversal copy() { + return new NarsetsReversal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NaturalBalance.java b/Mage.Sets/src/mage/cards/n/NaturalBalance.java index 37a8f22490..ca4658a053 100644 --- a/Mage.Sets/src/mage/cards/n/NaturalBalance.java +++ b/Mage.Sets/src/mage/cards/n/NaturalBalance.java @@ -92,7 +92,7 @@ public final class NaturalBalance extends CardImpl { 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, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game); } toShuffle.add(player); diff --git a/Mage.Sets/src/mage/cards/n/NaturesBlessing.java b/Mage.Sets/src/mage/cards/n/NaturesBlessing.java index 0b24af291c..4a40038167 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesBlessing.java +++ b/Mage.Sets/src/mage/cards/n/NaturesBlessing.java @@ -1,9 +1,5 @@ - package mage.cards.n; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.DiscardCardCost; @@ -27,14 +23,17 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author L_J */ public final class NaturesBlessing extends CardImpl { public NaturesBlessing(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}{W}"); // {G}{W}, Discard a card: Put a +1/+1 counter on target creature or that creature gains banding, first strike, or trample. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new NaturesBlessingEffect(), new ManaCostsImpl("{G}{W}")); @@ -102,7 +101,7 @@ class NaturesBlessingEffect extends OneShotEffect { if (gainedAbility != null) { game.addEffect(new GainAbilityTargetEffect(gainedAbility, Duration.Custom), source); } else { - targetPermanent.getCounters(game).addCounter(CounterType.P1P1.createInstance()); + targetPermanent.addCounters(CounterType.P1P1.createInstance(), source, game); game.informPlayers(controller.getLogName() + " puts a +1/+1 counter on " + targetPermanent.getLogName()); } return true; diff --git a/Mage.Sets/src/mage/cards/n/Neoform.java b/Mage.Sets/src/mage/cards/n/Neoform.java new file mode 100644 index 0000000000..56cd8b5d21 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/Neoform.java @@ -0,0 +1,152 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +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.StaticFilters; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Neoform extends CardImpl { + + public Neoform(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}{U}"); + + // As an additional cost to cast this spell, sacrifice a creature. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent( + 1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT, true + ))); + + // Search your library for a creature card with converted mana cost equal to 1 plus the sacrificed creature's converted mana cost, + // put that card onto the battlefield with an additional +1/+1 counter on it, then shuffle your library. + this.getSpellAbility().addEffect(new NeoformEffect()); + } + + private Neoform(final Neoform card) { + super(card); + } + + @Override + public Neoform copy() { + return new Neoform(this); + } +} + +class NeoformEffect extends OneShotEffect { + + NeoformEffect() { + super(Outcome.Benefit); + staticText = "Search your library for a creature card with converted mana cost equal to " + + "1 plus the sacrificed creature's converted mana cost, " + + "put that card onto the battlefield with an additional +1/+1 counter on it, then shuffle your library."; + } + + private NeoformEffect(final NeoformEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sacrificedPermanent = null; + for (Cost cost : source.getCosts()) { + if (cost instanceof SacrificeTargetCost) { + SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; + if (!sacrificeCost.getPermanents().isEmpty()) { + sacrificedPermanent = sacrificeCost.getPermanents().get(0); + } + break; + } + } + Player controller = game.getPlayer(source.getControllerId()); + if (sacrificedPermanent == null || controller == null) { + return false; + } + int newConvertedCost = sacrificedPermanent.getConvertedManaCost() + 1; + FilterCard filter = new FilterCard("creature card with converted mana cost " + newConvertedCost); + filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, newConvertedCost)); + filter.add(new CardTypePredicate(CardType.CREATURE)); + TargetCardInLibrary target = new TargetCardInLibrary(filter); + if (controller.searchLibrary(target, source, game)) { + Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); + if (card != null) { + ContinuousEffectImpl effect = new NeoformReplacementEffect(); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + if (!controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { + effect.discard(); + } + } + } + controller.shuffleLibrary(source, game); + return true; + } + + @Override + public NeoformEffect copy() { + return new NeoformEffect(this); + } +} + +class NeoformReplacementEffect extends ReplacementEffectImpl { + + NeoformReplacementEffect() { + super(Duration.EndOfStep, Outcome.BoostCreature); + } + + private NeoformReplacementEffect(NeoformReplacementEffect effect) { + super(effect); + } + + @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 event.getTargetId().equals(getTargetPointer().getFirst(game, source)); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget(); + if (creature != null) { + creature.addCounters(CounterType.P1P1.createInstance(), source, game, event.getAppliedEffects()); + } + discard(); + return false; + } + + @Override + public NeoformReplacementEffect copy() { + return new NeoformReplacementEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NeverendingTorment.java b/Mage.Sets/src/mage/cards/n/NeverendingTorment.java index 0f3d26ed80..7df007f4fc 100644 --- a/Mage.Sets/src/mage/cards/n/NeverendingTorment.java +++ b/Mage.Sets/src/mage/cards/n/NeverendingTorment.java @@ -65,7 +65,7 @@ class NeverendingTormentEffect extends OneShotEffect { if (targetPlayer != null && you != null) { TargetCardInLibrary target = new TargetCardInLibrary(you.getHand().size(), new FilterCard()); - you.searchLibrary(target, game, targetPlayer.getId()); + you.searchLibrary(target, source, game, targetPlayer.getId()); for (UUID cardId : target.getTargets()) { final Card targetCard = game.getCard(cardId); if (targetCard != null) { diff --git a/Mage.Sets/src/mage/cards/n/NewFrontiers.java b/Mage.Sets/src/mage/cards/n/NewFrontiers.java index 340bb18a7e..0e5357b3a7 100644 --- a/Mage.Sets/src/mage/cards/n/NewFrontiers.java +++ b/Mage.Sets/src/mage/cards/n/NewFrontiers.java @@ -64,7 +64,7 @@ class NewFrontiersEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null && player.chooseUse(outcome, "Search your library for up to " + amount + " basic lands?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java new file mode 100644 index 0000000000..5c6e26462c --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java @@ -0,0 +1,215 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.common.FilterPlaneswalkerPermanent; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreatureOrPlaneswalker; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageObject; +import mage.cards.Card; + +/** + * @author TheElk801 + */ +public final class NicolBolasDragonGod extends CardImpl { + + public NicolBolasDragonGod(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{U}{B}{B}{B}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.BOLAS); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Nicol Bolas, Dragon-God has all loyalty abilities of all other planeswalkers on the battlefield. + this.addAbility(new SimpleStaticAbility(new NicolBolasDragonGodGainAbilitiesEffect())); + + // +1: You draw a card. Each opponent exiles a card from their hand or a permanent they control. + this.addAbility(new LoyaltyAbility(new NicolBolasDragonGodPlusOneEffect(), 1)); + + // -3: Destroy target creature or planeswalker. + Ability ability = new LoyaltyAbility(new DestroyTargetEffect(), -3); + ability.addTarget(new TargetCreatureOrPlaneswalker()); + this.addAbility(ability); + + // -8: Each opponent who doesn't control a legendary creature or planeswalker loses the game. + this.addAbility(new LoyaltyAbility(new NicolBolasDragonGodMinus8Effect(), -8)); + } + + private NicolBolasDragonGod(final NicolBolasDragonGod card) { + super(card); + } + + @Override + public NicolBolasDragonGod copy() { + return new NicolBolasDragonGod(this); + } +} + +class NicolBolasDragonGodGainAbilitiesEffect extends ContinuousEffectImpl { + + private static final FilterPermanent filter = new FilterPlaneswalkerPermanent(); + + static { + filter.add(AnotherPredicate.instance); + } + + NicolBolasDragonGodGainAbilitiesEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "{this} has all loyalty abilities of all other planeswalkers on the battlefield."; + } + + private NicolBolasDragonGodGainAbilitiesEffect(final NicolBolasDragonGodGainAbilitiesEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent perm = game.getPermanent(source.getSourceId()); + if (perm == null) { + return true; + } + for (Permanent permanent : game.getState().getBattlefield().getActivePermanents( + filter, source.getControllerId(), source.getSourceId(), game + )) { + for (Ability ability : permanent.getAbilities()) { + if (ability instanceof LoyaltyAbility) { + perm.addAbility(ability, source.getSourceId(), game); + } + } + } + return true; + } + + @Override + public NicolBolasDragonGodGainAbilitiesEffect copy() { + return new NicolBolasDragonGodGainAbilitiesEffect(this); + } +} + +class NicolBolasDragonGodPlusOneEffect extends OneShotEffect { + + NicolBolasDragonGodPlusOneEffect() { + super(Outcome.Benefit); + staticText = "You draw a card. Each opponent exiles a card from their " + + "hand or a permanent they control."; + } + + private NicolBolasDragonGodPlusOneEffect(final NicolBolasDragonGodPlusOneEffect effect) { + super(effect); + } + + @Override + public NicolBolasDragonGodPlusOneEffect copy() { + return new NicolBolasDragonGodPlusOneEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Boolean applied = false; + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.drawCards(1, game); + Set cardsOnBattlefield = new LinkedHashSet<>(); + Set cards = new LinkedHashSet<>(); + for (UUID opponentId : game.getState().getPlayersInRange(player.getId(), game)) { + if (!player.hasOpponent(opponentId, game)) { + continue; + } + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + if (opponent.getHand().isEmpty() + || !opponent.chooseUse(outcome, "Exile a card in your hand or a permanent you control?", + null, "Card in hand", "Permanent", source, game)) { + TargetPermanent target = new TargetControlledPermanent(); + target.setNotTarget(true); + target.setTargetController(opponentId); + if (opponent.choose(outcome, target, source.getSourceId(), game)) { + MageObject mageObject = game.getObject(target.getFirstTarget()); + if (mageObject != null + && mageObject instanceof Permanent) { + cardsOnBattlefield.add((Card) mageObject); + } + } + } else { + TargetCardInHand target = new TargetCardInHand(); + if (opponent.choose(outcome, opponent.getHand(), target, game) + && game.getCard(target.getFirstTarget()) != null) { + cards.add(game.getCard(target.getFirstTarget())); + } + } + } + cards.addAll(cardsOnBattlefield); + for (Card card : cards) { + if (card != null) { + Player owner = game.getPlayer(card.getOwnerId()); + if (owner != null + && owner.moveCards(card, Zone.EXILED, source, game)) { + applied = true; + } + } + } + return applied; + } +} + +class NicolBolasDragonGodMinus8Effect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent(); + + static { + filter.add(new SupertypePredicate(SuperType.LEGENDARY)); + } + + NicolBolasDragonGodMinus8Effect() { + super(Outcome.Benefit); + staticText = "Each opponent who doesn't control a legendary creature or planeswalker loses the game."; + } + + private NicolBolasDragonGodMinus8Effect(final NicolBolasDragonGodMinus8Effect effect) { + super(effect); + } + + @Override + public NicolBolasDragonGodMinus8Effect copy() { + return new NicolBolasDragonGodMinus8Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID opponentId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null || !opponent.hasOpponent(source.getControllerId(), game)) { + continue; + } + if (game.getBattlefield().getAllActivePermanents(filter, opponentId, game).isEmpty()) { + opponent.lost(game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NightDealings.java b/Mage.Sets/src/mage/cards/n/NightDealings.java index 033f544c21..ae7c7011a2 100644 --- a/Mage.Sets/src/mage/cards/n/NightDealings.java +++ b/Mage.Sets/src/mage/cards/n/NightDealings.java @@ -155,7 +155,7 @@ public final class NightDealings extends CardImpl { filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, cmc)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { card.moveToZone(Zone.HAND, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/n/NightmareIncursion.java b/Mage.Sets/src/mage/cards/n/NightmareIncursion.java index 078fd20904..f2af74211b 100644 --- a/Mage.Sets/src/mage/cards/n/NightmareIncursion.java +++ b/Mage.Sets/src/mage/cards/n/NightmareIncursion.java @@ -80,7 +80,7 @@ class NightmareIncursionEffect extends OneShotEffect { if (controller != null && targetPlayer != null) { int amount = new PermanentsOnBattlefieldCount(filter).calculate(game, source, this); TargetCardInLibrary target = new TargetCardInLibrary(0, amount, new FilterCard()); - if (controller.searchLibrary(target, game, targetPlayer.getId())) { + if (controller.searchLibrary(target, source, game, targetPlayer.getId())) { List targetId = target.getTargets(); for (UUID targetCard : targetId) { Card card = targetPlayer.getLibrary().remove(targetCard, game); diff --git a/Mage.Sets/src/mage/cards/n/NissaWhoShakesTheWorld.java b/Mage.Sets/src/mage/cards/n/NissaWhoShakesTheWorld.java new file mode 100644 index 0000000000..4156d5ea05 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NissaWhoShakesTheWorld.java @@ -0,0 +1,149 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.TriggeredManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.command.emblems.NissaWhoShakesTheWorldEmblem; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TokenImpl; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NissaWhoShakesTheWorld extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledLandPermanent("noncreature land you control"); + private static final FilterCard filter2 = new FilterCard("Forest cards"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.CREATURE))); + filter2.add(new SubtypePredicate(SubType.FOREST)); + } + + public NissaWhoShakesTheWorld(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{G}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.NISSA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Whenever you tap a Forest for mana, add an additional {G}. + this.addAbility(new NissaWhoShakesTheWorldTriggeredAbility()); + + // +1: Put three +1/+1 counters on up to one target noncreature land you control. Untap it. It becomes a 0/0 Elemental creature with vigilance and haste that's still a land. + Ability ability = new LoyaltyAbility(new AddCountersTargetEffect( + CounterType.P1P1.createInstance(3) + ), 1); + ability.addEffect(new UntapTargetEffect().setText("Untap it.")); + ability.addEffect(new BecomesCreatureTargetEffect( + new NissaWhoShakesTheWorldToken(), false, true, Duration.Custom + ).setText("It becomes a 0/0 Elemental creature with vigilance and haste that's still a land.")); + ability.addTarget(new TargetPermanent(0, 1, filter, false)); + this.addAbility(ability); + + // -8: You get an emblem with "Lands you control have indestructible." Search your library for any number of Forest cards, put them onto the battlefield tapped, then shuffle your library. + ability = new LoyaltyAbility(new GetEmblemEffect(new NissaWhoShakesTheWorldEmblem()), -8); + ability.addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary( + 0, Integer.MAX_VALUE, filter2 + ), true)); + this.addAbility(ability); + } + + private NissaWhoShakesTheWorld(final NissaWhoShakesTheWorld card) { + super(card); + } + + @Override + public NissaWhoShakesTheWorld copy() { + return new NissaWhoShakesTheWorld(this); + } +} + +class NissaWhoShakesTheWorldTriggeredAbility extends TriggeredManaAbility { + + private static final FilterControlledLandPermanent filter = new FilterControlledLandPermanent("Forest"); + + static { + filter.add(new SubtypePredicate(SubType.FOREST)); + } + + NissaWhoShakesTheWorldTriggeredAbility() { + super(Zone.BATTLEFIELD, new BasicManaEffect(Mana.GreenMana(1)), false); + this.usesStack = false; + } + + private NissaWhoShakesTheWorldTriggeredAbility(final NissaWhoShakesTheWorldTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TAPPED_FOR_MANA; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent land = game.getPermanent(event.getTargetId()); + return land != null && filter.match(land, this.getSourceId(), this.getControllerId(), game); + } + + @Override + public NissaWhoShakesTheWorldTriggeredAbility copy() { + return new NissaWhoShakesTheWorldTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever you tap a Forest for mana, add an additional {G}."; + } +} + +class NissaWhoShakesTheWorldToken extends TokenImpl { + + NissaWhoShakesTheWorldToken() { + super("", "0/0 Elemental creature with vigilance and haste that's still a land."); + this.cardType.add(CardType.CREATURE); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + this.addAbility(HasteAbility.getInstance()); + this.addAbility(VigilanceAbility.getInstance()); + } + + private NissaWhoShakesTheWorldToken(final NissaWhoShakesTheWorldToken token) { + super(token); + } + + public NissaWhoShakesTheWorldToken copy() { + return new NissaWhoShakesTheWorldToken(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NissaWorldwaker.java b/Mage.Sets/src/mage/cards/n/NissaWorldwaker.java index d4bb863132..ff0d0eecc9 100644 --- a/Mage.Sets/src/mage/cards/n/NissaWorldwaker.java +++ b/Mage.Sets/src/mage/cards/n/NissaWorldwaker.java @@ -89,7 +89,7 @@ class NissaWorldwakerSearchEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { Card card = controller.getLibrary().getCard(cardId, game); diff --git a/Mage.Sets/src/mage/cards/n/NissasEncouragement.java b/Mage.Sets/src/mage/cards/n/NissasEncouragement.java index 030d75b2d9..a94f770385 100644 --- a/Mage.Sets/src/mage/cards/n/NissasEncouragement.java +++ b/Mage.Sets/src/mage/cards/n/NissasEncouragement.java @@ -77,7 +77,7 @@ class NissasEncouragementEffect extends OneShotEffect { } NissasEncouragementTarget target = new NissasEncouragementTarget(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { boolean searchGY = false; if (target.getTargets().size() < 3) { diff --git a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java index af4dda2a14..14663db780 100644 --- a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java +++ b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java @@ -81,7 +81,7 @@ class NissasPilgrimageEffect extends OneShotEffect { number++; } TargetCardInLibrary target = new TargetCardInLibrary(0, number, filter); - controller.searchLibrary(target, game); + controller.searchLibrary(target, source, game); if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(target.getTargets()); controller.revealCards(sourceObject.getIdName(), cards, game); diff --git a/Mage.Sets/src/mage/cards/n/NissasTriumph.java b/Mage.Sets/src/mage/cards/n/NissasTriumph.java new file mode 100644 index 0000000000..8d5a5ceb7f --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NissasTriumph.java @@ -0,0 +1,63 @@ +package mage.cards.n; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NissasTriumph extends CardImpl { + + private static final FilterCard filter = new FilterCard("basic Forest cards"); + private static final FilterPermanent filter2 = new FilterControlledPlaneswalkerPermanent(SubType.NISSA); + + static { + filter.add(new SupertypePredicate(SuperType.BASIC)); + filter.add(new SubtypePredicate(SubType.FOREST)); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); + + public NissasTriumph(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}{G}"); + + // Search your land for up to two basic Forest cards. If you control a Nissa planeswalker, instead search your library for up to three land cards. reveal those cards, put them in your hand, then shuffle your library. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary( + 0, 3, StaticFilters.FILTER_CARD_LAND + ), true, true), + new SearchLibraryPutInHandEffect(new TargetCardInLibrary( + 0, 2, filter + ), true, true), + new PermanentsOnTheBattlefieldCondition(filter2), + "Search your library for up to two basic Forest cards. If you control a Nissa planeswalker, " + + "instead search your library for up to three land cards. " + + "Reveal those cards, put them into your hand, then shuffle your library." + )); + } + + private NissasTriumph(final NissasTriumph card) { + super(card); + } + + @Override + public NissasTriumph copy() { + return new NissasTriumph(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java b/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java new file mode 100644 index 0000000000..fe55aa3aae --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java @@ -0,0 +1,164 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NivMizzetReborn extends CardImpl { + + public NivMizzetReborn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}"); + + this.addSuperType(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()); + + // When Niv-Mizzet Reborn enters the battlefield, reveal the top ten cards of your library. For each color pair, choose a card that's exactly those colors from among them. Put the chosen cards into your hand and the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new NivMizzetRebornEffect())); + } + + private NivMizzetReborn(final NivMizzetReborn card) { + super(card); + } + + @Override + public NivMizzetReborn copy() { + return new NivMizzetReborn(this); + } +} + +class NivMizzetRebornEffect extends OneShotEffect { + + private static enum Guild { + G0("W", "U"), G1("W", "B"), G2("U", "B"), G3("U", "R"), G4("B", "R"), + G5("B", "G"), G6("R", "G"), G7("R", "W"), G8("G", "W"), G9("G", "U"); + + private static final Map nameMap = new HashMap(); + + static { + nameMap.put("W", "white"); + nameMap.put("U", "blue"); + nameMap.put("B", "black"); + nameMap.put("R", "red"); + nameMap.put("G", "green"); + } + + private final String color1; + private final String color2; + + private Guild(String color1, String color2) { + this.color1 = color1; + this.color2 = color2; + } + + private FilterCard makeFilter() { + FilterCard filter = new FilterCard(getDescription()); + filter.add(new ColorPredicate(new ObjectColor(color1))); + filter.add(new ColorPredicate(new ObjectColor(color2))); + for (char c : getOtherColors().toCharArray()) { + filter.add(Predicates.not(new ColorPredicate(new ObjectColor("" + c)))); + } + return filter; + } + + private TargetCard getTarget() { + return new TargetCardInLibrary(makeFilter()); + } + + private String getDescription() { + return "card that is exactly " + nameMap.get(color1) + " and " + nameMap.get(color2); + } + + private String getOtherColors() { + String colors = color1 + color2; + String otherColors = ""; + for (char c : "WUBRG".toCharArray()) { + if (color1.charAt(0) == c || color2.charAt(0) == c) { + continue; + } + otherColors += c; + } + return otherColors; + } + + private boolean isInCards(Cards cards, Game game) { + FilterCard filter = makeFilter(); + return cards.getCards(game).stream().anyMatch(card -> filter.match(card, game)); + } + } + + NivMizzetRebornEffect() { + super(Outcome.Benefit); + staticText = "reveal the top ten cards of your library. For each color pair, " + + "choose a card that's exactly those colors from among them. " + + "Put the chosen cards into your hand and the rest on the bottom of your library in a random order."; + } + + private NivMizzetRebornEffect(final NivMizzetRebornEffect effect) { + super(effect); + } + + @Override + public NivMizzetRebornEffect copy() { + return new NivMizzetRebornEffect(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, 10)); + Cards cards2 = new CardsImpl(); + if (cards.isEmpty()) { + return false; + } + player.revealCards(source, cards, game); + for (Guild guild : Guild.values()) { + if (!guild.isInCards(cards, game)) { + continue; + } + TargetCard target = guild.getTarget(); + if (player.choose(outcome, cards, target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + cards2.add(card); + } + } + } + cards.removeAll(cards2); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + if (player.moveCards(cards2, Zone.HAND, source, game)) { + for (Card card : cards2.getCards(game)) { + game.informPlayers(player.getName() + " chose " + card.getName() + " and put it into his or her hand."); + } + } + return true; + } +} +// I think this is my favorite card I've ever implemented diff --git a/Mage.Sets/src/mage/cards/n/NobleBenefactor.java b/Mage.Sets/src/mage/cards/n/NobleBenefactor.java index 61b5936ff1..d223291a3c 100644 --- a/Mage.Sets/src/mage/cards/n/NobleBenefactor.java +++ b/Mage.Sets/src/mage/cards/n/NobleBenefactor.java @@ -71,7 +71,7 @@ class NobleBenefactorEffect extends OneShotEffect { if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(); if (player.chooseUse(Outcome.Benefit, "Search your library for a card to put into your hand?", source, game)) { - player.searchLibrary(target, game); + player.searchLibrary(target, source, game); for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().getCard(cardId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/o/OathOfKaya.java b/Mage.Sets/src/mage/cards/o/OathOfKaya.java new file mode 100644 index 0000000000..235d0d0ff7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OathOfKaya.java @@ -0,0 +1,98 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OathOfKaya extends CardImpl { + + public OathOfKaya(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + + // When Oath of Kaya enters the battlefield, it deals 3 damage to any target and you gain 3 life. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3, "it")); + ability.addEffect(new GainLifeEffect(3).concatBy("and")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // Whenever an opponent attacks a planeswalker you control with one or more creatures, + // Oath of Kaya deals 2 damage to that player and you gain 2 life. + this.addAbility(new OathOfKayaTriggeredAbility()); + } + + private OathOfKaya(final OathOfKaya card) { + super(card); + } + + @Override + public OathOfKaya copy() { + return new OathOfKaya(this); + } +} + +class OathOfKayaTriggeredAbility extends TriggeredAbilityImpl { + + public OathOfKayaTriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(2), false); + this.addEffect(new GainLifeEffect(2)); + } + + public OathOfKayaTriggeredAbility(final OathOfKayaTriggeredAbility ability) { + super(ability); + } + + @Override + public OathOfKayaTriggeredAbility copy() { + return new OathOfKayaTriggeredAbility(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 you = game.getPlayer(this.getControllerId()); + if (you == null) { + return false; + } + + if (game.getCombat().isPlaneswalkerAttacked(you.getId(), game)) { + for (UUID attacker : game.getCombat().getAttackers()) { + Permanent attackingPermanent = game.getPermanent(attacker); + if (attackingPermanent != null && attackingPermanent.isCreature()) { + getEffects().setTargetPointer(new FixedTarget(attackingPermanent.getControllerId(), game)); + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever an opponent attacks a planeswalker you control with one or more creatures, " + + "{this} deals 2 damage to that player and you gain 2 life."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java index a6ebc1b4c3..409a599c1b 100644 --- a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java +++ b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java @@ -71,7 +71,7 @@ class OldGrowthDryadsEffect extends OneShotEffect { Player opponent = game.getPlayer(opponentId); if (opponent != null && opponent.chooseUse(Outcome.PutLandInPlay, "Search your library for a basic land card and put it onto the battlefield tapped?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(new FilterBasicLandCard()); - if (opponent.searchLibrary(target, game)) { + if (opponent.searchLibrary(target, source, game)) { Card targetCard = opponent.getLibrary().getCard(target.getFirstTarget(), game); if (targetCard != null) { opponent.moveCards(targetCard, Zone.BATTLEFIELD, source, game, true, false, false, null); diff --git a/Mage.Sets/src/mage/cards/o/OracleEnVec.java b/Mage.Sets/src/mage/cards/o/OracleEnVec.java index 768e057bfe..04f6d21370 100644 --- a/Mage.Sets/src/mage/cards/o/OracleEnVec.java +++ b/Mage.Sets/src/mage/cards/o/OracleEnVec.java @@ -42,7 +42,9 @@ public final class OracleEnVec extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - // {tap}: Target opponent chooses any number of creatures he or she controls. During that player's next turn, the chosen creatures attack if able, and other creatures can't attack. At the beginning of that turn's end step, destroy each of the chosen creatures that didn't attack. Activate this ability only during your turn. + // {T}: Target opponent chooses any number of creatures they control. During that player’s next turn, the chosen + // creatures attack if able, and other creatures can’t attack. At the beginning of that turn’s end step, + // destroy each of the chosen creatures that didn’t attack this turn. Activate this ability only during your turn. Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, new OracleEnVecEffect(), new TapSourceCost(), MyTurnCondition.instance); ability.addTarget(new TargetOpponent()); this.addAbility(ability, new AttackedThisTurnWatcher()); @@ -62,7 +64,9 @@ class OracleEnVecEffect extends OneShotEffect { OracleEnVecEffect() { super(Outcome.Benefit); - this.staticText = "Target opponent chooses any number of creatures he or she controls. During that player's next turn, the chosen creatures attack if able, and other creatures can't attack. At the beginning of that turn's end step, destroy each of the chosen creatures that didn't attack"; + this.staticText = "Target opponent chooses any number of creatures he or she controls. During that player's next turn, " + + "the chosen creatures attack if able, and other creatures can't attack. At the beginning of that turn's end step, " + + "destroy each of the chosen creatures that didn't attack"; } OracleEnVecEffect(final OracleEnVecEffect effect) { @@ -102,7 +106,7 @@ class OracleEnVecEffect extends OneShotEffect { class OracleEnVecMustAttackRequirementEffect extends RequirementEffect { OracleEnVecMustAttackRequirementEffect() { - super(Duration.Custom); + super(Duration.UntilEndOfYourNextTurn); } OracleEnVecMustAttackRequirementEffect(final OracleEnVecMustAttackRequirementEffect effect) { @@ -117,7 +121,8 @@ class OracleEnVecMustAttackRequirementEffect extends RequirementEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { return this.getTargetPointer().getFirst(game, source) != null - && this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); + && this.getTargetPointer().getFirst(game, source).equals(permanent.getId()) + && this.isYourNextTurn(game); } @Override @@ -130,11 +135,20 @@ class OracleEnVecMustAttackRequirementEffect extends RequirementEffect { return false; } + @Override + public void init(Ability source, Game game) { + super.init(source, game); + Permanent perm = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + if (perm != null) { + setStartingControllerAndTurnNum(game, perm.getControllerId(), game.getActivePlayerId()); // setup startingController to calc isYourTurn calls + } else { + discard(); + } + } + @Override public boolean isInactive(Ability source, Game game) { - return startingTurn != game.getTurnNum() - && (game.getPhase().getType() == TurnPhase.END - && game.isActivePlayer(this.getTargetPointer().getFirst(game, source))); + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); } @Override @@ -169,11 +183,20 @@ class OracleEnVecCantAttackRestrictionEffect extends RestrictionEffect { return false; } + @Override + public void init(Ability source, Game game) { + super.init(source, game); + Permanent perm = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + if (perm != null) { + setStartingControllerAndTurnNum(game, perm.getControllerId(), game.getActivePlayerId()); // setup startingController to calc isYourTurn calls + } else { + discard(); + } + } + @Override public boolean isInactive(Ability source, Game game) { - return startingTurn != game.getTurnNum() - && (game.getPhase().getType() == TurnPhase.END - && game.isActivePlayer(this.getTargetPointer().getFirst(game, source))); + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OreskosExplorer.java b/Mage.Sets/src/mage/cards/o/OreskosExplorer.java index ceda298514..8325374b9c 100644 --- a/Mage.Sets/src/mage/cards/o/OreskosExplorer.java +++ b/Mage.Sets/src/mage/cards/o/OreskosExplorer.java @@ -87,7 +87,7 @@ class OreskosExplorerEffect extends OneShotEffect { filterPlains.add(new ControllerPredicate(TargetController.YOU)); filterPlains.add(new SubtypePredicate(SubType.PLAINS)); TargetCardInLibrary target = new TargetCardInLibrary(0, landsToSearch, filterPlains); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Cards cards = new CardsImpl(target.getTargets()); controller.revealCards(sourceObject.getIdName(), cards, game); controller.moveCards(cards.getCards(game), Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/o/OrimsPrayer.java b/Mage.Sets/src/mage/cards/o/OrimsPrayer.java index b171246ae4..61a6a42b8d 100644 --- a/Mage.Sets/src/mage/cards/o/OrimsPrayer.java +++ b/Mage.Sets/src/mage/cards/o/OrimsPrayer.java @@ -2,7 +2,6 @@ package mage.cards.o; import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.dynamicvalue.common.AttackingCreatureCount; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -11,6 +10,8 @@ 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; /** * @@ -36,9 +37,11 @@ public final class OrimsPrayer extends CardImpl { } class OrimsPrayerTriggeredAbility extends TriggeredAbilityImpl { + + int numberAttackingController = 0; public OrimsPrayerTriggeredAbility() { - super(Zone.BATTLEFIELD, new GainLifeEffect(new AttackingCreatureCount())); + super(Zone.BATTLEFIELD, null); } public OrimsPrayerTriggeredAbility(final OrimsPrayerTriggeredAbility ability) { @@ -57,12 +60,28 @@ class OrimsPrayerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - return game.getCombat().getDefenders().contains(getControllerId()) - && game.getCombat().getAttackers().size() > 0; + boolean applied = false; + Player controller = game.getPlayer(getControllerId()); + if (controller == null) { + return false; + } + for (UUID attackersId : game.getCombat().getAttackers()) { + Permanent attackingCreature = game.getPermanent(attackersId); + if (attackingCreature != null + && game.getCombat().getDefenderId(attackersId) == this.getControllerId()) { + numberAttackingController += 1; + applied = true; + } + } + if (applied + && numberAttackingController > 0) { + this.getEffects().add(new GainLifeEffect(numberAttackingController)); + } + return applied; } @Override public String getRule() { - return "Whenever one or more creatures attack you, " + super.getRule(); + return "Whenever one or more creatures attack you, you gain 1 life for each attacking creature."; } } diff --git a/Mage.Sets/src/mage/cards/o/OtarianJuggernaut.java b/Mage.Sets/src/mage/cards/o/OtarianJuggernaut.java index 3a5f723f34..39369dff34 100644 --- a/Mage.Sets/src/mage/cards/o/OtarianJuggernaut.java +++ b/Mage.Sets/src/mage/cards/o/OtarianJuggernaut.java @@ -1,29 +1,24 @@ - package mage.cards.o; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleEvasionAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CardsInControllerGraveCondition; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.Effect; +import mage.abilities.decorator.ConditionalRequirementEffect; import mage.abilities.effects.common.combat.AttacksIfAbleSourceEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityWord; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.SubtypePredicate; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class OtarianJuggernaut extends CardImpl { @@ -49,12 +44,11 @@ public final class OtarianJuggernaut extends CardImpl { new BoostSourceEffect(3, 0, Duration.WhileOnBattlefield), new CardsInControllerGraveCondition(7), "As long as seven or more cards are in your graveyard, {this} gets +3/+0")); - Effect effect = new ConditionalContinuousEffect( + ability.addEffect(new ConditionalRequirementEffect( new AttacksIfAbleSourceEffect(Duration.WhileOnBattlefield, true), new CardsInControllerGraveCondition(7), "and attacks each combat if able" - ); - ability.addEffect(effect); + )); ability.setAbilityWord(AbilityWord.THRESHOLD); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/o/Oubliette.java b/Mage.Sets/src/mage/cards/o/Oubliette.java index 57514db438..9e933be131 100644 --- a/Mage.Sets/src/mage/cards/o/Oubliette.java +++ b/Mage.Sets/src/mage/cards/o/Oubliette.java @@ -1,9 +1,5 @@ - package mage.cards.o; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -27,8 +23,11 @@ import mage.target.Target; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author MarcoMarin */ public final class Oubliette extends CardImpl { @@ -137,7 +136,7 @@ class OublietteReturnEffect extends OneShotEffect { if (counters != null) { for (Counter counter : counters.values()) { if (counter != null) { - newPermanent.getCounters(game).addCounter(counter); + newPermanent.getCounters(game).addCounter(counter); // it's restore counters, not add (e.g. without add events) } } } diff --git a/Mage.Sets/src/mage/cards/p/ParallelThoughts.java b/Mage.Sets/src/mage/cards/p/ParallelThoughts.java index e000b52815..16306d9f34 100644 --- a/Mage.Sets/src/mage/cards/p/ParallelThoughts.java +++ b/Mage.Sets/src/mage/cards/p/ParallelThoughts.java @@ -77,7 +77,7 @@ class ParallelThoughtsSearchEffect extends OneShotEffect { if (controller != null && permanent != null) { TargetCardInLibrary target = new TargetCardInLibrary(7, new FilterCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { for (UUID targetId : target.getTargets()) { Card card = controller.getLibrary().getCard(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/p/ParhelionII.java b/Mage.Sets/src/mage/cards/p/ParhelionII.java new file mode 100644 index 0000000000..4ccd337e3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ParhelionII.java @@ -0,0 +1,60 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FirstStrikeAbility; +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.SubType; +import mage.constants.SuperType; +import mage.game.permanent.token.AngelVigilanceToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ParhelionII extends CardImpl { + + public ParhelionII(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Parhelion II attacks, create two 4/4 white Angel creature tokens with flying and vigilance that are attacking. + this.addAbility(new AttacksTriggeredAbility(new CreateTokenEffect( + new AngelVigilanceToken(), 2, + false, true + ), false)); + + // Crew 4 + this.addAbility(new CrewAbility(4)); + + } + + private ParhelionII(final ParhelionII card) { + super(card); + } + + @Override + public ParhelionII copy() { + return new ParhelionII(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PathToExile.java b/Mage.Sets/src/mage/cards/p/PathToExile.java index 591ac25107..d4cb6a64ac 100644 --- a/Mage.Sets/src/mage/cards/p/PathToExile.java +++ b/Mage.Sets/src/mage/cards/p/PathToExile.java @@ -69,7 +69,7 @@ class PathToExileEffect extends OneShotEffect { controller.moveCardToExileWithInfo(permanent, null, "", source.getSourceId(), game, Zone.BATTLEFIELD, true); if (player.chooseUse(Outcome.PutCardInPlay, "Search your library for a basic land card?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); diff --git a/Mage.Sets/src/mage/cards/p/PaupersCage.java b/Mage.Sets/src/mage/cards/p/PaupersCage.java index 961a57eecb..fcfad9135d 100644 --- a/Mage.Sets/src/mage/cards/p/PaupersCage.java +++ b/Mage.Sets/src/mage/cards/p/PaupersCage.java @@ -26,8 +26,9 @@ public final class PaupersCage extends CardImpl { // At the beginning of each opponent's upkeep, if that player has two or fewer cards in hand, Paupers' Cage deals 2 damage to him or her. TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new DamageTargetEffect(2), TargetController.OPPONENT, false, true); - CardsInHandCondition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 3); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, condition, "At the beginning of each opponent's upkeep, if that player has two or fewer cards in hand, {this} deals 2 damage to him or her.")); + CardsInHandCondition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 3, null, TargetController.ACTIVE); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, condition, + "At the beginning of each opponent's upkeep, if that player has two or fewer cards in hand, {this} deals 2 damage to him or her.")); } public PaupersCage(final PaupersCage card) { diff --git a/Mage.Sets/src/mage/cards/p/PeaceTalks.java b/Mage.Sets/src/mage/cards/p/PeaceTalks.java index ef9811c540..e50bbf11e3 100644 --- a/Mage.Sets/src/mage/cards/p/PeaceTalks.java +++ b/Mage.Sets/src/mage/cards/p/PeaceTalks.java @@ -71,13 +71,22 @@ class PeaceTalksEffect extends OneShotEffect { class PeaceTalksCantAttackEffect extends RestrictionEffect { + int startedTurnNum = 0; + public PeaceTalksCantAttackEffect() { super(Duration.Custom); staticText = "Creatures can't attack this turn and next turn"; } + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startedTurnNum = game.getTurnNum(); + } + public PeaceTalksCantAttackEffect(final PeaceTalksCantAttackEffect effect) { super(effect); + this.startedTurnNum = effect.startedTurnNum; } @Override @@ -97,7 +106,7 @@ class PeaceTalksCantAttackEffect extends RestrictionEffect { @Override public boolean isInactive(Ability source, Game game) { - if (startingTurn + 2 == game.getTurnNum()) { + if (game.getTurnNum() > (startedTurnNum + 1)) { this.discard(); return true; } @@ -107,6 +116,8 @@ class PeaceTalksCantAttackEffect extends RestrictionEffect { class PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities extends ContinuousRuleModifyingEffectImpl { + int startedTurnNum = 0; + public PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities() { super(Duration.Custom, Outcome.Neutral); staticText = "players and permanents can't be the targets of spells or activated abilities"; @@ -114,6 +125,13 @@ class PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities ex public PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities(final PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities effect) { super(effect); + this.startedTurnNum = effect.startedTurnNum; + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startedTurnNum = game.getTurnNum(); } @Override @@ -149,7 +167,7 @@ class PeaceTalksPlayersAndPermanentsCantBeTargetsOfSpellsOrActivatedAbilities ex @Override public boolean isInactive(Ability source, Game game) { - if (startingTurn + 2 == game.getTurnNum()) { + if (game.getTurnNum() > (startedTurnNum + 1)) { this.discard(); return true; } diff --git a/Mage.Sets/src/mage/cards/p/Peregrination.java b/Mage.Sets/src/mage/cards/p/Peregrination.java index 5f4bce46bf..53d6f14045 100644 --- a/Mage.Sets/src/mage/cards/p/Peregrination.java +++ b/Mage.Sets/src/mage/cards/p/Peregrination.java @@ -71,7 +71,7 @@ class PeregrinationEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianRager.java b/Mage.Sets/src/mage/cards/p/PhyrexianRager.java index 62f62d7d51..98e5594f5b 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianRager.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianRager.java @@ -1,8 +1,5 @@ - - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -13,24 +10,27 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** - * * @author Loki */ public final class PhyrexianRager extends CardImpl { - public PhyrexianRager (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}"); + public PhyrexianRager(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); this.subtype.add(SubType.HORROR); this.power = new MageInt(2); this.toughness = new MageInt(2); - Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false); - ability.addEffect(new LoseLifeSourceControllerEffect(1)); + Ability ability = new EntersBattlefieldTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); this.addAbility(ability); } - public PhyrexianRager (final PhyrexianRager card) { + public PhyrexianRager(final PhyrexianRager card) { super(card); } diff --git a/Mage.Sets/src/mage/cards/p/PirImaginativeRascal.java b/Mage.Sets/src/mage/cards/p/PirImaginativeRascal.java index 3679e5533d..3f2bc145da 100644 --- a/Mage.Sets/src/mage/cards/p/PirImaginativeRascal.java +++ b/Mage.Sets/src/mage/cards/p/PirImaginativeRascal.java @@ -1,27 +1,21 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.keyword.PartnerWithAbility; -import mage.constants.SubType; -import mage.constants.SuperType; 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.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class PirImaginativeRascal extends CardImpl { @@ -65,10 +59,7 @@ class PirImaginativeRascalEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - int amount = event.getAmount(); - if (amount >= 1) { - event.setAmount(amount + 1); - } + event.setAmountForCounters(event.getAmount() + 1, true); return false; } @@ -84,7 +75,7 @@ class PirImaginativeRascalEffect extends ReplacementEffectImpl { if (permanent == null) { permanent = game.getPermanentEntering(event.getTargetId()); } - return permanent != null && player != null + return permanent != null && player != null && event.getAmount() > 0 && !player.hasOpponent(permanent.getControllerId(), game); } diff --git a/Mage.Sets/src/mage/cards/p/PirsWhim.java b/Mage.Sets/src/mage/cards/p/PirsWhim.java index 9ff41c79c6..b8da3732c6 100644 --- a/Mage.Sets/src/mage/cards/p/PirsWhim.java +++ b/Mage.Sets/src/mage/cards/p/PirsWhim.java @@ -70,7 +70,7 @@ class PirsWhimEffect extends OneShotEffect { for (Player player : choice.getFriends()) { if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, true, null); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/p/PlaguemawBeast.java b/Mage.Sets/src/mage/cards/p/PlaguemawBeast.java index 8bbb6cb49e..2a5dabc403 100644 --- a/Mage.Sets/src/mage/cards/p/PlaguemawBeast.java +++ b/Mage.Sets/src/mage/cards/p/PlaguemawBeast.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -13,11 +11,13 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT; import mage.target.common.TargetControlledCreaturePermanent; +import java.util.UUID; + +import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT; + /** - * * @author Loki */ public final class PlaguemawBeast extends CardImpl { @@ -28,6 +28,8 @@ public final class PlaguemawBeast extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(3); + + // {T}, Sacrifice a creature: Proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ProliferateEffect(), new TapSourceCost()); ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/p/PlanewideCelebration.java b/Mage.Sets/src/mage/cards/p/PlanewideCelebration.java new file mode 100644 index 0000000000..bc78247019 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PlanewideCelebration.java @@ -0,0 +1,56 @@ +package mage.cards.p; + +import mage.abilities.Mode; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.game.permanent.token.PlanewideCelebrationToken; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PlanewideCelebration extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("permanent card from your graveyard"); + + public PlanewideCelebration(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{G}{G}"); + + // Choose four. You may choose the same mode more than once. + this.getSpellAbility().getModes().setMinModes(4); + this.getSpellAbility().getModes().setMaxModes(4); + this.getSpellAbility().getModes().setEachModeMoreThanOnce(true); + + // • Create a 2/2 Citizen creature token that's all colors. + this.getSpellAbility().addEffect(new CreateTokenEffect(new PlanewideCelebrationToken())); + + // • Return target permanent card from your graveyard to your hand. + Mode mode = new Mode(new ReturnToHandTargetEffect()); + mode.addTarget(new TargetCardInYourGraveyard(filter)); + this.getSpellAbility().addMode(mode); + + // • Proliferate. + this.getSpellAbility().addMode(new Mode(new ProliferateEffect(false))); + + // • You gain 4 life. + this.getSpellAbility().addMode(new Mode(new GainLifeEffect(4))); + } + + private PlanewideCelebration(final PlanewideCelebration card) { + super(card); + } + + @Override + public PlanewideCelebration copy() { + return new PlanewideCelebration(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PledgeOfUnity.java b/Mage.Sets/src/mage/cards/p/PledgeOfUnity.java new file mode 100644 index 0000000000..a183a351e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PledgeOfUnity.java @@ -0,0 +1,39 @@ +package mage.cards.p; + +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PledgeOfUnity extends CardImpl { + + public PledgeOfUnity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}{W}"); + + // Put a +1/+1 counter on each creature you control. You gain 1 life for each creature you control. + this.getSpellAbility().addEffect(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + )); + this.getSpellAbility().addEffect(new GainLifeEffect( + new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE) + ).setText("You gain 1 life for each creature you control.")); + } + + private PledgeOfUnity(final PledgeOfUnity card) { + super(card); + } + + @Override + public PledgeOfUnity copy() { + return new PledgeOfUnity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PollenbrightDruid.java b/Mage.Sets/src/mage/cards/p/PollenbrightDruid.java index 2669624b02..cb51f26780 100644 --- a/Mage.Sets/src/mage/cards/p/PollenbrightDruid.java +++ b/Mage.Sets/src/mage/cards/p/PollenbrightDruid.java @@ -35,7 +35,7 @@ public final class PollenbrightDruid extends CardImpl { ); ability.addTarget(new TargetCreaturePermanent()); - // • Proliferate. + // • Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) ability.addMode(new Mode(new ProliferateEffect())); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PraetorsGrasp.java b/Mage.Sets/src/mage/cards/p/PraetorsGrasp.java index 832e284f34..9f20c8703d 100644 --- a/Mage.Sets/src/mage/cards/p/PraetorsGrasp.java +++ b/Mage.Sets/src/mage/cards/p/PraetorsGrasp.java @@ -68,7 +68,7 @@ class PraetorsGraspEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && opponent != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (controller.searchLibrary(target, game, opponent.getId())) { + if (controller.searchLibrary(target, source, game, opponent.getId())) { UUID targetId = target.getFirstTarget(); Card card = opponent.getLibrary().getCard(targetId, game); UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); diff --git a/Mage.Sets/src/mage/cards/p/PrecognitionField.java b/Mage.Sets/src/mage/cards/p/PrecognitionField.java index 46ab2eac8f..80a9b352d4 100644 --- a/Mage.Sets/src/mage/cards/p/PrecognitionField.java +++ b/Mage.Sets/src/mage/cards/p/PrecognitionField.java @@ -1,4 +1,3 @@ - package mage.cards.p; import mage.MageObject; @@ -7,8 +6,8 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -27,14 +26,13 @@ public final class PrecognitionField extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); // You may look at the top card of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PrecognitionFieldTopCardRevealedEffect())); + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast the top card of your library if it's an instant or sorcery card. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PrecognitionFieldTopCardCastEffect())); + this.addAbility(new SimpleStaticAbility(new PrecognitionFieldTopCardCastEffect())); // {3}: Exile the top card of your library. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, - new PrecognitionFieldExileEffect(), new GenericManaCost(3))); + this.addAbility(new SimpleActivatedAbility(new PrecognitionFieldExileEffect(), new GenericManaCost(3))); } public PrecognitionField(final PrecognitionField card) { @@ -47,38 +45,6 @@ public final class PrecognitionField extends CardImpl { } } -class PrecognitionFieldTopCardRevealedEffect extends ContinuousEffectImpl { - - public PrecognitionFieldTopCardRevealedEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - staticText = "You may look at the top card of your library any time."; - } - - public PrecognitionFieldTopCardRevealedEffect(final PrecognitionFieldTopCardRevealedEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Card topCard = controller.getLibrary().getFromTop(game); - if (topCard != null) { - MageObject precognitionField = source.getSourceObject(game); - if (precognitionField != null) { - controller.lookAtCards("Top card of " + precognitionField.getIdName() + " controller's library", topCard, game); - } - } - } - return true; - } - - @Override - public PrecognitionFieldTopCardRevealedEffect copy() { - return new PrecognitionFieldTopCardRevealedEffect(this); - } -} - class PrecognitionFieldTopCardCastEffect extends AsThoughEffectImpl { public PrecognitionFieldTopCardCastEffect() { diff --git a/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java b/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java new file mode 100644 index 0000000000..25b2ce6cc7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java @@ -0,0 +1,132 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterOpponent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterPermanentOrPlayer; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetPermanentOrPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PriceOfBetrayal extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.PLANESWALKER) + )); + } + + private static final FilterPermanentOrPlayer filter2 = new FilterPermanentOrPlayer("artifact, creature, planeswalker, or opponent", filter, new FilterOpponent()); + + public PriceOfBetrayal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // Remove up to five counters from target artifact, creature, planeswalker, or opponent. + this.getSpellAbility().addEffect(new PriceOfBetrayalEffect()); + this.getSpellAbility().addTarget(new TargetPermanentOrPlayer(1, 1, filter2, false)); + } + + private PriceOfBetrayal(final PriceOfBetrayal card) { + super(card); + } + + @Override + public PriceOfBetrayal copy() { + return new PriceOfBetrayal(this); + } +} + +class PriceOfBetrayalEffect extends OneShotEffect { + + PriceOfBetrayalEffect() { + super(Outcome.Benefit); + staticText = "Remove up to five counters from target artifact, creature, planeswalker, or opponent."; + } + + private PriceOfBetrayalEffect(final PriceOfBetrayalEffect effect) { + super(effect); + } + + @Override + public PriceOfBetrayalEffect copy() { + return new PriceOfBetrayalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + int toRemove = 5; + int removed = 0; + String[] counterNames = permanent.getCounters(game).keySet().toArray(new String[0]); + for (String counterName : counterNames) { + if (controller.chooseUse(Outcome.Neutral, "Do you want to remove " + counterName + " counters?", source, game)) { + if (permanent.getCounters(game).get(counterName).getCount() == 1 || toRemove == 1) { + permanent.removeCounters(counterName, 1, game); + removed++; + } else { + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + if (amount > 0) { + removed += amount; + permanent.removeCounters(counterName, amount, game); + } + } + } + if (removed >= toRemove) { + break; + } + } + game.addEffect(new BoostSourceEffect(removed, 0, Duration.EndOfTurn), source); + return true; + } + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + int toRemove = 5; + int removed = 0; + String[] counterNames = player.getCounters().keySet().toArray(new String[0]); + for (String counterName : counterNames) { + if (controller.chooseUse(Outcome.Neutral, "Do you want to remove " + counterName + " counters?", source, game)) { + if (player.getCounters().get(counterName).getCount() == 1 || toRemove == 1) { + player.removeCounters(counterName, 1, source, game); + removed++; + } else { + int amount = controller.getAmount(1, Math.min(player.getCounters().get(counterName).getCount(), toRemove - removed), "How many?", game); + if (amount > 0) { + removed += amount; + player.removeCounters(counterName, amount, source, game); + } + } + } + if (removed >= toRemove) { + break; + } + } + game.addEffect(new BoostSourceEffect(removed, 0, Duration.EndOfTurn), source); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PrideOfConquerors.java b/Mage.Sets/src/mage/cards/p/PrideOfConquerors.java index 00b7eefb38..02a41fe538 100644 --- a/Mage.Sets/src/mage/cards/p/PrideOfConquerors.java +++ b/Mage.Sets/src/mage/cards/p/PrideOfConquerors.java @@ -5,6 +5,7 @@ import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.keyword.AscendEffect; import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.PermanentsYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -22,12 +23,13 @@ public final class PrideOfConquerors extends CardImpl { // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) this.getSpellAbility().addEffect(new AscendEffect()); + this.getSpellAbility().addHint(CitysBlessingHint.instance); + this.getSpellAbility().addHint(PermanentsYouControlHint.instance); // Creatures you control get +1/+1 until end of turn. If you have the city's blessing, those creatures get +2/+2 until end of turn instead. this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn), new BoostControlledEffect(1, 1, Duration.EndOfTurn), CitysBlessingCondition.instance, "Creatures you control get +1/+1 until end of turn. If you have the city's blessing, those creatures get +2/+2 until end of turn instead")); - this.getSpellAbility().addHint(CitysBlessingHint.instance); } public PrideOfConquerors(final PrideOfConquerors card) { diff --git a/Mage.Sets/src/mage/cards/p/PrimalVigor.java b/Mage.Sets/src/mage/cards/p/PrimalVigor.java index 184b1bc499..7443eca630 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalVigor.java +++ b/Mage.Sets/src/mage/cards/p/PrimalVigor.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; @@ -15,14 +13,15 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PrimalVigor extends CardImpl { public PrimalVigor(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}"); // If one or more tokens would be created, twice that many of those tokens are created instead. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PrimalVigorTokenEffect())); @@ -93,7 +92,7 @@ class PrimalVigorCounterEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() * 2); + event.setAmountForCounters(event.getAmount() * 2, true); return false; } @@ -108,11 +107,8 @@ class PrimalVigorCounterEffect extends ReplacementEffectImpl { if (permanent == null) { permanent = game.getPermanentEntering(event.getTargetId()); } - if (permanent != null && permanent.isCreature() - && event.getData() != null && event.getData().equals("+1/+1")) { - return true; - } - return false; + return permanent != null && event.getAmount() > 0 && permanent.isCreature() + && event.getData() != null && event.getData().equals("+1/+1"); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PrimeSpeakerVannifar.java b/Mage.Sets/src/mage/cards/p/PrimeSpeakerVannifar.java index 3554ba25ad..e0c0313c7e 100644 --- a/Mage.Sets/src/mage/cards/p/PrimeSpeakerVannifar.java +++ b/Mage.Sets/src/mage/cards/p/PrimeSpeakerVannifar.java @@ -92,7 +92,7 @@ class PrimeSpeakerVannifarEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, newConvertedCost)); filter.add(new CardTypePredicate(CardType.CREATURE)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/p/Prismite.java b/Mage.Sets/src/mage/cards/p/Prismite.java new file mode 100644 index 0000000000..1f1794c28c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/Prismite.java @@ -0,0 +1,37 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Prismite extends CardImpl { + + public Prismite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.GOLEM); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {2}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility(new GenericManaCost(2))); + } + + private Prismite(final Prismite card) { + super(card); + } + + @Override + public Prismite copy() { + return new Prismite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrisonRealm.java b/Mage.Sets/src/mage/cards/p/PrisonRealm.java new file mode 100644 index 0000000000..e22f4296d9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrisonRealm.java @@ -0,0 +1,55 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PrisonRealm extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public PrisonRealm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // When Prison Realm enters the battlefield, exile target creature or planeswalker an opponent controls until Prison Realm leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility( + new ExileUntilSourceLeavesEffect(filter.getMessage()) + ); + ability.addTarget(new TargetPermanent(filter)); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); + this.addAbility(ability); + + // When Prison Realm enters the battlefield, scry 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1))); + } + + private PrisonRealm(final PrisonRealm card) { + super(card); + } + + @Override + public PrisonRealm copy() { + return new PrisonRealm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrivilegedPosition.java b/Mage.Sets/src/mage/cards/p/PrivilegedPosition.java index 98315cd3fc..9ef623711a 100644 --- a/Mage.Sets/src/mage/cards/p/PrivilegedPosition.java +++ b/Mage.Sets/src/mage/cards/p/PrivilegedPosition.java @@ -10,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; /** * @@ -23,7 +23,7 @@ public final class PrivilegedPosition extends CardImpl { // Other permanents you control have hexproof. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, new FilterPermanent(), true))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENTS, true))); } public PrivilegedPosition(final PrivilegedPosition card) { diff --git a/Mage.Sets/src/mage/cards/p/ProteanHydra.java b/Mage.Sets/src/mage/cards/p/ProteanHydra.java index 571ba1c5c4..ca3820ba66 100644 --- a/Mage.Sets/src/mage/cards/p/ProteanHydra.java +++ b/Mage.Sets/src/mage/cards/p/ProteanHydra.java @@ -3,12 +3,11 @@ package mage.cards.p; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.PreventDamageAndRemoveCountersEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -16,13 +15,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; /** * @@ -41,7 +38,7 @@ public final class ProteanHydra extends CardImpl { this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))); // If damage would be dealt to Protean Hydra, prevent that damage and remove that many +1/+1 counters from it. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ProteanHydraEffect2())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect())); // Whenever a +1/+1 counter is removed from Protean Hydra, put two +1/+1 counters on it at the beginning of the next end step. this.addAbility(new ProteanHydraAbility()); @@ -57,50 +54,6 @@ public final class ProteanHydra extends CardImpl { return new ProteanHydra(this); } - static class ProteanHydraEffect2 extends PreventionEffectImpl { - - public ProteanHydraEffect2() { - super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false); - staticText = "If damage would be dealt to {this}, prevent that damage and remove that many +1/+1 counters from it"; - } - - public ProteanHydraEffect2(final ProteanHydraEffect2 effect) { - super(effect); - } - - @Override - public ProteanHydraEffect2 copy() { - return new ProteanHydraEffect2(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - int damage = event.getAmount(); - preventDamageAction(event, source, game); - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - permanent.removeCounters(CounterType.P1P1.createInstance(damage), game); //MTG ruling Protean Hydra loses counters even if the damage isn't prevented - } - return false; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (super.applies(event, source, game)) { - if (event.getTargetId().equals(source.getSourceId())) { - return true; - } - } - return false; - } - - } - class ProteanHydraAbility extends TriggeredAbilityImpl { public ProteanHydraAbility() { diff --git a/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java b/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java index aae9d64c1b..ac4ddff684 100644 --- a/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java +++ b/Mage.Sets/src/mage/cards/p/ProtectiveSphere.java @@ -52,20 +52,20 @@ public final class ProtectiveSphere extends CardImpl { class ProtectiveSphereEffect extends PreventionEffectImpl { private final TargetSource target; - private Mana manaUsed; - private List colorsOfChosenSource = new ArrayList<>(); + private static Mana manaUsed; + private static List colorsOfChosenSource = new ArrayList<>(); public ProtectiveSphereEffect() { super(Duration.EndOfTurn, Integer.MAX_VALUE, false, false); - this.staticText = "Prevent all damage that would be dealt to you this turn by a source of your choice that shares a color with the mana spent on this activation cost."; + this.staticText = "Prevent all damage that would be dealt to you " + + "this turn by a source of your choice that shares a color " + + "with the mana spent on this activation cost."; this.target = new TargetSource(); } public ProtectiveSphereEffect(final ProtectiveSphereEffect effect) { super(effect); this.target = effect.target.copy(); - manaUsed = effect.manaUsed.copy(); - colorsOfChosenSource = effect.colorsOfChosenSource; } @Override @@ -81,8 +81,12 @@ class ProtectiveSphereEffect extends PreventionEffectImpl { Permanent protectiveSphere = game.getPermanent(source.getSourceId()); if (controller != null && protectiveSphere != null) { - game.getState().setValue("ProtectiveSphere" + source.getSourceId().toString(), source.getManaCostsToPay().getUsedManaToPay()); //store the mana used to pay - protectiveSphere.addInfo("MANA USED", CardUtil.addToolTipMarkTags("Last mana used for protective ability: " + source.getManaCostsToPay().getUsedManaToPay()), game); + game.getState().setValue("ProtectiveSphere" + + source.getSourceId().toString(), + source.getManaCostsToPay().getUsedManaToPay()); //store the mana used to pay + protectiveSphere.addInfo("MANA USED", + CardUtil.addToolTipMarkTags("Last mana used for protective ability: " + + source.getManaCostsToPay().getUsedManaToPay()), game); } this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); super.init(source, game); diff --git a/Mage.Sets/src/mage/cards/p/Pteramander.java b/Mage.Sets/src/mage/cards/p/Pteramander.java index 6a98bcf192..2172b5de82 100644 --- a/Mage.Sets/src/mage/cards/p/Pteramander.java +++ b/Mage.Sets/src/mage/cards/p/Pteramander.java @@ -44,7 +44,7 @@ public final class Pteramander extends CardImpl { Ability ability = new SimpleActivatedAbility(new AdaptEffect(4).setText("Adapt 4. This ability costs {1} less to activate for each instant and sorcery card in your graveyard."), new ManaCostsImpl("{7}{U}")); this.addAbility(ability); this.addAbility(new SimpleStaticAbility(Zone.ALL, new PteramanderCostIncreasingEffect(ability.getOriginalId())) - .addHint(new ValueHint("Instant and sorcery card in your graveyard", cardsCount))); + .addHint(new ValueHint("Instant and sorcery cards in your graveyard", cardsCount))); } private Pteramander(final Pteramander card) { diff --git a/Mage.Sets/src/mage/cards/q/QuestForTheHolyRelic.java b/Mage.Sets/src/mage/cards/q/QuestForTheHolyRelic.java index ac0e51c6aa..984098d5fe 100644 --- a/Mage.Sets/src/mage/cards/q/QuestForTheHolyRelic.java +++ b/Mage.Sets/src/mage/cards/q/QuestForTheHolyRelic.java @@ -80,7 +80,7 @@ class QuestForTheHolyRelicEffect extends OneShotEffect { FilterCard filter = new FilterCard("Equipment"); filter.add(new SubtypePredicate(SubType.EQUIPMENT)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null && controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { Permanent equipment = game.getPermanent(card.getId()); diff --git a/Mage.Sets/src/mage/cards/q/QuietSpeculation.java b/Mage.Sets/src/mage/cards/q/QuietSpeculation.java index d189e274aa..9fafe47689 100644 --- a/Mage.Sets/src/mage/cards/q/QuietSpeculation.java +++ b/Mage.Sets/src/mage/cards/q/QuietSpeculation.java @@ -73,7 +73,7 @@ class SearchLibraryPutInGraveEffect extends SearchEffect { if (controller == null) { return false; } - if (targetPlayerID != null && controller.searchLibrary(target, game, targetPlayerID)) { + if (targetPlayerID != null && controller.searchLibrary(target, source, game, targetPlayerID)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(target.getTargets()); controller.revealCards("Quiet Speculation", cards, game); diff --git a/Mage.Sets/src/mage/cards/q/QuirionDruid.java b/Mage.Sets/src/mage/cards/q/QuirionDruid.java new file mode 100644 index 0000000000..f96dcb089b --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QuirionDruid.java @@ -0,0 +1,71 @@ +package mage.cards.q; + +import mage.MageInt; +import mage.ObjectColor; +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.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.constants.Zone; +import mage.game.permanent.token.TokenImpl; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * + * @author jmharmon + */ + +public final class QuirionDruid extends CardImpl { + + public QuirionDruid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DRUID); + + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {G}, {T}: Target land becomes a 2/2 green creature that’s still a land. (This effect lasts indefinitely.) + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesCreatureTargetEffect(new QuirionDruidToken(), false, true, Duration.Custom), new ManaCostsImpl("{G}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetLandPermanent()); + this.addAbility(ability); + } + + public QuirionDruid(final QuirionDruid card) { + super(card); + } + + @Override + public QuirionDruid copy() { + return new QuirionDruid(this); + } +} + +class QuirionDruidToken extends TokenImpl { + + public QuirionDruidToken() { + super("", "2/2 green creature"); + this.color.addColor(ObjectColor.GREEN); + this.cardType.add(CardType.CREATURE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + } + + public QuirionDruidToken(final QuirionDruidToken token) { + super(token); + } + + public QuirionDruidToken copy() { + return new QuirionDruidToken(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RagingKronch.java b/Mage.Sets/src/mage/cards/r/RagingKronch.java new file mode 100644 index 0000000000..6073012f68 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RagingKronch.java @@ -0,0 +1,36 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.keyword.CantAttackAloneAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RagingKronch extends CardImpl { + + public RagingKronch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Raging Kronch can't attack alone. + this.addAbility(new CantAttackAloneAbility()); + } + + private RagingKronch(final RagingKronch card) { + super(card); + } + + @Override + public RagingKronch copy() { + return new RagingKronch(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RalStormConduit.java b/Mage.Sets/src/mage/cards/r/RalStormConduit.java new file mode 100644 index 0000000000..ca9a9e2911 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RalStormConduit.java @@ -0,0 +1,138 @@ +package mage.cards.r; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.LoyaltyAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.common.TargetOpponentOrPlaneswalker; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RalStormConduit extends CardImpl { + + public RalStormConduit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{U}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.RAL); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Whenever you cast or copy an instant or sorcery spell, Ral, Storm Conduit deals 1 damage to target opponent or planeswalker. + this.addAbility(new RalStormConduitTriggeredAbility()); + + // +2: Scry 1. + this.addAbility(new LoyaltyAbility(new ScryEffect(1), 2)); + + // -2: When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy. + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( + new RalStormConduitDelayedTriggeredAbility() + ).setText("When you cast your next instant or sorcery spell this turn, " + + "copy that spell. You may choose new targets for the copy" + ), -2)); + } + + private RalStormConduit(final RalStormConduit card) { + super(card); + } + + @Override + public RalStormConduit copy() { + return new RalStormConduit(this); + } +} + +class RalStormConduitTriggeredAbility extends TriggeredAbilityImpl { + + RalStormConduitTriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(1), false); + this.addTarget(new TargetOpponentOrPlaneswalker()); + } + + private RalStormConduitTriggeredAbility(final RalStormConduitTriggeredAbility effect) { + super(effect); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case COPIED_STACKOBJECT: + case SPELL_CAST: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Spell spell = game.getSpell(event.getTargetId()); + return spell != null && spell.isControlledBy(getControllerId()) && spell.isInstantOrSorcery(); + } + + @Override + public RalStormConduitTriggeredAbility copy() { + return new RalStormConduitTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever you cast or copy an instant or sorcery spell, " + + "{this} deals 1 damage to target opponent or planeswalker."; + } +} + +class RalStormConduitDelayedTriggeredAbility extends DelayedTriggeredAbility { + + RalStormConduitDelayedTriggeredAbility() { + super(new CopyTargetSpellEffect(true), Duration.EndOfTurn); + } + + private RalStormConduitDelayedTriggeredAbility(final RalStormConduitDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public RalStormConduitDelayedTriggeredAbility copy() { + return new RalStormConduitDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(this.getControllerId())) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && (spell.isInstant() || spell.isSorcery())) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + } + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "When you cast your next instant or sorcery spell this turn, " + + "copy that spell. You may choose new targets for the copy."; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RallyOfWings.java b/Mage.Sets/src/mage/cards/r/RallyOfWings.java new file mode 100644 index 0000000000..f63a6ca6b4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RallyOfWings.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RallyOfWings extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("Creatures you control with flying"); + + static { + filter.add(new AbilityPredicate(FlyingAbility.class)); + filter.add(new ControllerPredicate(TargetController.YOU)); + } + + public RallyOfWings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Untap all creatures you control. Creatures you control with flying get +2/+2 until end of turn. + this.getSpellAbility().addEffect(new UntapAllControllerEffect(StaticFilters.FILTER_PERMANENT_CREATURES)); + this.getSpellAbility().addEffect(new BoostAllEffect(2, 2, Duration.EndOfTurn, filter, false)); + } + + private RallyOfWings(final RallyOfWings card) { + super(card); + } + + @Override + public RallyOfWings copy() { + return new RallyOfWings(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RalsOutburst.java b/Mage.Sets/src/mage/cards/r/RalsOutburst.java new file mode 100644 index 0000000000..40944d7951 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RalsOutburst.java @@ -0,0 +1,40 @@ +package mage.cards.r; + +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RalsOutburst extends CardImpl { + + public RalsOutburst(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{R}"); + + // Ral's Outburst deals 3 damage to any target. Look at the top two cards of your library. Put one of them into your hand and the other into your graveyard. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + new StaticValue(2), false, new StaticValue(1), + StaticFilters.FILTER_CARD, Zone.GRAVEYARD, false, false + )); + } + + private RalsOutburst(final RalsOutburst card) { + super(card); + } + + @Override + public RalsOutburst copy() { + return new RalsOutburst(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RavnicaAtWar.java b/Mage.Sets/src/mage/cards/r/RavnicaAtWar.java index 6967b7849b..4fde6d2102 100644 --- a/Mage.Sets/src/mage/cards/r/RavnicaAtWar.java +++ b/Mage.Sets/src/mage/cards/r/RavnicaAtWar.java @@ -5,7 +5,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.MulticoloredPredicate; import java.util.UUID; @@ -25,7 +24,7 @@ public final class RavnicaAtWar extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}"); // Exile all multicolored permanents. - this.getSpellAbility().addEffect(new ExileAllEffect(StaticFilters.FILTER_PERMANENT)); + this.getSpellAbility().addEffect(new ExileAllEffect(filter)); } private RavnicaAtWar(final RavnicaAtWar card) { diff --git a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java index 0e7f04e6e3..399fb4494f 100644 --- a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java +++ b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java @@ -71,7 +71,7 @@ class RealmsUnchartedEffect extends OneShotEffect { } RealmsUnchartedTarget target = new RealmsUnchartedTarget(); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/r/ReapIntellect.java b/Mage.Sets/src/mage/cards/r/ReapIntellect.java index 53fb7286a7..50e28670a3 100644 --- a/Mage.Sets/src/mage/cards/r/ReapIntellect.java +++ b/Mage.Sets/src/mage/cards/r/ReapIntellect.java @@ -128,7 +128,7 @@ class ReapIntellectEffect extends OneShotEffect { // search cards in Library TargetCardInLibrary targetCardsLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCards); - controller.searchLibrary(targetCardsLibrary, game, targetPlayer.getId()); + controller.searchLibrary(targetCardsLibrary, source, game, targetPlayer.getId()); for (UUID cardId : targetCardsLibrary.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/r/RenownedWeaponsmith.java b/Mage.Sets/src/mage/cards/r/RenownedWeaponsmith.java index 8898379400..083aeb0eb1 100644 --- a/Mage.Sets/src/mage/cards/r/RenownedWeaponsmith.java +++ b/Mage.Sets/src/mage/cards/r/RenownedWeaponsmith.java @@ -113,7 +113,7 @@ class RenownedWeaponsmithEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); if (sourceObject != null && controller != null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(); Card card = game.getCard(target.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/r/RescuerSphinx.java b/Mage.Sets/src/mage/cards/r/RescuerSphinx.java new file mode 100644 index 0000000000..6987050ccd --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RescuerSphinx.java @@ -0,0 +1,100 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +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.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RescuerSphinx extends CardImpl { + + public RescuerSphinx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + + this.subtype.add(SubType.SPHINX); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // As Rescuer Sphinx enters the battlefield, you may return a nonland permanent you control to its owner's hand. If you do, Rescuer Sphinx enters the battlefield with a +1/+1 counter on it. + this.addAbility(new AsEntersBattlefieldAbility(new RescuerSphinxEffect())); + } + + private RescuerSphinx(final RescuerSphinx card) { + super(card); + } + + @Override + public RescuerSphinx copy() { + return new RescuerSphinx(this); + } +} + +class RescuerSphinxEffect extends OneShotEffect { + + private static final FilterPermanent filter + = new FilterControlledPermanent("nonland permanent you control"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + RescuerSphinxEffect() { + super(Outcome.Benefit); + staticText = "you may return a nonland permanent you control to its owner's hand. " + + "If you do, {this} enters the battlefield with a +1/+1 counter on it."; + } + + private RescuerSphinxEffect(final RescuerSphinxEffect effect) { + super(effect); + } + + @Override + public RescuerSphinxEffect copy() { + return new RescuerSphinxEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + if (!player.chooseUse(outcome, "Return a nonland permanent you control to your hand?", source, game)) { + return false; + } + Target target = new TargetPermanent(0, 1, filter, true); + if (!player.choose(outcome, target, source.getSourceId(), game)) { + return false; + } + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null || !player.moveCards(permanent, Zone.HAND, source, game)) { + return false; + } + return new AddCountersSourceEffect(CounterType.P1P1.createInstance()).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/ReturnToNature.java b/Mage.Sets/src/mage/cards/r/ReturnToNature.java new file mode 100644 index 0000000000..05f8100159 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReturnToNature.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetEnchantmentPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ReturnToNature extends CardImpl { + + public ReturnToNature(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Choose one — + // • Destroy target artifact. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetArtifactPermanent()); + + // • Destroy target enchantment. + Mode mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetEnchantmentPermanent()); + this.getSpellAbility().addMode(mode); + + // • Exile target card from a graveyard. + mode = new Mode(new ExileTargetEffect()); + mode.addTarget(new TargetCardInGraveyard()); + this.getSpellAbility().addMode(mode); + } + + private ReturnToNature(final ReturnToNature card) { + super(card); + } + + @Override + public ReturnToNature copy() { + return new ReturnToNature(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RevelOfTheFallenGod.java b/Mage.Sets/src/mage/cards/r/RevelOfTheFallenGod.java index d5bf111ff4..615c684023 100644 --- a/Mage.Sets/src/mage/cards/r/RevelOfTheFallenGod.java +++ b/Mage.Sets/src/mage/cards/r/RevelOfTheFallenGod.java @@ -1,15 +1,15 @@ package mage.cards.r; -import java.util.UUID; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.game.permanent.token.RevelOfTheFallenGodSatyrToken; +import mage.game.permanent.token.XenagosSatyrToken; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class RevelOfTheFallenGod extends CardImpl { @@ -18,7 +18,7 @@ public final class RevelOfTheFallenGod extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}{G}{G}"); // Create four 2/2 red and green Satyr creature tokens with haste. - this.getSpellAbility().addEffect(new CreateTokenEffect(new RevelOfTheFallenGodSatyrToken(), 4)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new XenagosSatyrToken(), 4)); } diff --git a/Mage.Sets/src/mage/cards/r/RoaleskApexHybrid.java b/Mage.Sets/src/mage/cards/r/RoaleskApexHybrid.java new file mode 100644 index 0000000000..6c77689b4a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoaleskApexHybrid.java @@ -0,0 +1,72 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +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.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RoaleskApexHybrid extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("another target creature you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public RoaleskApexHybrid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MUTANT); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Roalesk, Apex Hybrid enters the battlefield, put two +1/+1 counters on another target creature you control. + Ability ability = new EntersBattlefieldTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)) + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // When Roalsk dies, proliferate, then proliferate again. (Choose any number of permanents and/or players, then give each another counter of each kind already there. Then do it again.) + ability = new DiesTriggeredAbility(new ProliferateEffect(false)); + ability.addEffect(new ProliferateEffect(" again", true).concatBy(", then")); + this.addAbility(ability); + } + + private RoaleskApexHybrid(final RoaleskApexHybrid card) { + super(card); + } + + @Override + public RoaleskApexHybrid copy() { + return new RoaleskApexHybrid(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RodOfSpanking.java b/Mage.Sets/src/mage/cards/r/RodOfSpanking.java new file mode 100644 index 0000000000..34c2506033 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RodOfSpanking.java @@ -0,0 +1,80 @@ + +package mage.cards.r; + +import java.util.UUID; + +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.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.UntapSourceEffect; +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.TargetPlayer; + +/** + * + * @author North + */ +public final class RodOfSpanking extends CardImpl { + + public RodOfSpanking(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT }, "{1}"); + + // 2, T: Rod of Spanking deals 1 damage to target player. Then untap Rod of + // Spanking unless that player says "Thank you, sir. May I have another?" + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RodOfSpankingEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + public RodOfSpanking(final RodOfSpanking card) { + super(card); + } + + @Override + public RodOfSpanking copy() { + return new RodOfSpanking(this); + } +} + +class RodOfSpankingEffect extends OneShotEffect { + + public RodOfSpankingEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 1 damage to target player. Then untap {this} unless that player says \"Thank you, sir. May I have another?\""; + } + + public RodOfSpankingEffect(final RodOfSpankingEffect effect) { + super(effect); + } + + @Override + public RodOfSpankingEffect copy() { + return new RodOfSpankingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player target = game.getPlayer(source.getFirstTarget()); + new DamageTargetEffect(1).apply(game, source); + if (target != null) { + if (target.chooseUse(Outcome.Untap, "Say \"Thank you, sir. May I have another?\"", source, game)) { + game.informPlayers(target.getLogName() + ": Thank you, sir. May I have another?"); + } else { + new UntapSourceEffect().apply(game, source); + } + return true; + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/r/RogueSkycaptain.java b/Mage.Sets/src/mage/cards/r/RogueSkycaptain.java new file mode 100644 index 0000000000..029c8fff97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RogueSkycaptain.java @@ -0,0 +1,103 @@ +package mage.cards.r; + +import java.util.Set; +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +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.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetOpponent; + +/** + * + * @author Ketsuban + */ +public class RogueSkycaptain extends CardImpl { + + public RogueSkycaptain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{2}{R}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of your upkeep, put a wage counter on Rogue Skycaptain. You + // may pay 2 for each wage counter on it. If you don't, remove all wage counters + // from Rogue Skycaptain and an opponent gains control of it. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new RogueSkycaptainEffect(), TargetController.YOU, false)); + } + + @Override + public Card copy() { + return null; + } + +} + +class RogueSkycaptainEffect extends OneShotEffect { + + public RogueSkycaptainEffect() { + super(Outcome.GainControl); + staticText = "put a wage counter on {this}. You may pay {2} for each wage counter on it. If you don't, remove all wage counters from {this} and an opponent gains control of it"; + } + + public RogueSkycaptainEffect(final RogueSkycaptainEffect effect) { + super(effect); + } + + @Override + public Effect copy() { + return new RogueSkycaptainEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (controller != null && permanent != null) { + new AddCountersSourceEffect(CounterType.WAGE.createInstance(), true).apply(game, source); + Cost cost = new GenericManaCost(2 * permanent.getCounters(game).getCount(CounterType.WAGE)); + if (!cost.pay(source, game, controller.getId(), controller.getId(), false)) { + new RemoveAllCountersSourceEffect(CounterType.WAGE).apply(game, source); + Player opponent; + Set opponents = game.getOpponents(controller.getId()); + if (opponents.size() == 1) { + opponent = game.getPlayer(opponents.iterator().next()); + } else { + Target target = new TargetOpponent(true); + target.setNotTarget(true); + target.choose(Outcome.GainControl, source.getControllerId(), source.getSourceId(), game); + opponent = game.getPlayer(target.getFirstTarget()); + } + if (opponent != null) { + permanent.changeControllerId(opponent.getId(), game); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RootwaterThief.java b/Mage.Sets/src/mage/cards/r/RootwaterThief.java index 0da71f36ad..4b58f29638 100644 --- a/Mage.Sets/src/mage/cards/r/RootwaterThief.java +++ b/Mage.Sets/src/mage/cards/r/RootwaterThief.java @@ -76,7 +76,7 @@ class RootwaterThiefEffect extends OneShotEffect { if(controller.chooseUse(Outcome.Benefit, message, source, game) && cost.pay(source, game, source.getSourceId(), controller.getId(), false, null)) { TargetCardInLibrary target = new TargetCardInLibrary(); - if (controller.searchLibrary(target, game, damagedPlayer.getId())) { + if (controller.searchLibrary(target, source, game, damagedPlayer.getId())) { if (!target.getTargets().isEmpty()) { Card card = damagedPlayer.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/r/RowanKenrith.java b/Mage.Sets/src/mage/cards/r/RowanKenrith.java index 02e5889861..647cd80fb6 100644 --- a/Mage.Sets/src/mage/cards/r/RowanKenrith.java +++ b/Mage.Sets/src/mage/cards/r/RowanKenrith.java @@ -92,20 +92,21 @@ class RowanKenrithAttackEffect extends RequirementEffect { public void init(Ability source, Game game) { super.init(source, game); creatingPermanent = new MageObjectReference(source.getSourceId(), game); + setStartingControllerAndTurnNum(game, source.getFirstTarget(), game.getActivePlayerId()); // setup startingController to calc isYourTurn calls } @Override public boolean applies(Permanent permanent, Ability source, Game game) { - return permanent.isControlledBy(source.getFirstTarget()); + return permanent.isControlledBy(source.getFirstTarget()) && this.isYourNextTurn(game); } @Override public boolean isInactive(Ability source, Game game) { - return (startingTurn != game.getTurnNum() - && (game.getPhase().getType() == TurnPhase.END - && game.isActivePlayer(source.getFirstTarget()))) - || // 6/15/2010: If a creature controlled by the affected player can't attack Gideon Jura (because he's no longer on the battlefield, for example), that player may have it attack you, another one of your planeswalkers, or nothing at all. - creatingPermanent.getPermanent(game) == null; + return (game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game)) + // 6/15/2010: If a creature controlled by the affected player can't attack Gideon Jura + // (because he's no longer on the battlefield, for example), that player may have it attack you, + // another one of your planeswalkers, or nothing at all. + || creatingPermanent.getPermanent(game) == null; } @Override diff --git a/Mage.Sets/src/mage/cards/r/RubblebeltRioters.java b/Mage.Sets/src/mage/cards/r/RubblebeltRioters.java new file mode 100644 index 0000000000..836019948d --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RubblebeltRioters.java @@ -0,0 +1,48 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RubblebeltRioters extends CardImpl { + + public RubblebeltRioters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Rubblebelt Rioters attacks, it gets +X/+0 until end of turn, where X is the greatest power among creatures you control. + this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect( + GreatestPowerAmongControlledCreaturesValue.instance, new StaticValue(0), + Duration.EndOfTurn, true + ), false)); + } + + private RubblebeltRioters(final RubblebeltRioters card) { + super(card); + } + + @Override + public RubblebeltRioters copy() { + return new RubblebeltRioters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RuinInTheirWake.java b/Mage.Sets/src/mage/cards/r/RuinInTheirWake.java index a4e9705c67..1d2d0530ba 100644 --- a/Mage.Sets/src/mage/cards/r/RuinInTheirWake.java +++ b/Mage.Sets/src/mage/cards/r/RuinInTheirWake.java @@ -71,7 +71,7 @@ class RuinInTheirWakeEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { Cards cardsToReveal = new CardsImpl(card); diff --git a/Mage.Sets/src/mage/cards/r/RunechantersPike.java b/Mage.Sets/src/mage/cards/r/RunechantersPike.java index b01ba3853a..8c61e3ba2a 100644 --- a/Mage.Sets/src/mage/cards/r/RunechantersPike.java +++ b/Mage.Sets/src/mage/cards/r/RunechantersPike.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -35,15 +34,21 @@ public final class RunechantersPike extends CardImpl { } public RunechantersPike(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{2}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); this.subtype.add(SubType.EQUIPMENT); // Equip {2} this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(2))); // Equipped creature has first strike and gets +X/+0 where X is the number of instant and sorcery cards in your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(new RunechantersPikeValue(), new StaticValue(0)))); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT))); + Effect effect = new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT); + Effect effect2 = new BoostEquippedEffect(new RunechantersPikeValue(), new StaticValue(0)); + effect.setText("Equipped creature has first strike"); + effect2.setText(" and gets +X/+0 where X is the number of instant and sorcery cards in your graveyard."); + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); + ability.addEffect(effect2); + this.addAbility(ability); + } public RunechantersPike(final RunechantersPike card) { diff --git a/Mage.Sets/src/mage/cards/s/SadisticSacrament.java b/Mage.Sets/src/mage/cards/s/SadisticSacrament.java index f1f7a64ede..2f1b08a70b 100644 --- a/Mage.Sets/src/mage/cards/s/SadisticSacrament.java +++ b/Mage.Sets/src/mage/cards/s/SadisticSacrament.java @@ -80,7 +80,7 @@ class SadisticSacramentEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && targetPlayer != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, amount, new FilterCard("cards to exile")); - if (player.searchLibrary(target, game, targetPlayer.getId())) { + if (player.searchLibrary(target, source, game, targetPlayer.getId())) { List targets = target.getTargets(); for (UUID targetId : targets) { Card card = targetPlayer.getLibrary().remove(targetId, game); diff --git a/Mage.Sets/src/mage/cards/s/SaheeliSublimeArtificer.java b/Mage.Sets/src/mage/cards/s/SaheeliSublimeArtificer.java new file mode 100644 index 0000000000..bb20999d03 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaheeliSublimeArtificer.java @@ -0,0 +1,113 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ServoToken; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.functions.EmptyApplyToPermanent; + +/** + * @author TheElk801 + */ +public final class SaheeliSublimeArtificer extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledArtifactPermanent(); + private static final FilterPermanent filter2 + = new FilterControlledPermanent("artifact or creature you control"); + + static { + filter.add(new AnotherTargetPredicate(1)); + filter2.add(new AnotherTargetPredicate(2)); + filter2.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.CREATURE) + )); + } + + public SaheeliSublimeArtificer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U/R}{U/R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SAHEELI); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Whenever you cast a noncreature spell, create a 1/1 colorless Servo artifact creature token. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new ServoToken()), StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + + // -2: Target artifact you control becomes a copy of another target artifact or creature you control until end of turn, except it's an artifact in addition to its other types. + Ability ability = new LoyaltyAbility(new SaheeliSublimeArtificerEffect(), -2); + Target target = new TargetPermanent(filter); + target.setTargetTag(1); + ability.addTarget(target); + target = new TargetPermanent(filter2); + target.setTargetTag(2); + ability.addTarget(target); + this.addAbility(ability); + } + + private SaheeliSublimeArtificer(final SaheeliSublimeArtificer card) { + super(card); + } + + @Override + public SaheeliSublimeArtificer copy() { + return new SaheeliSublimeArtificer(this); + } +} + +class SaheeliSublimeArtificerEffect extends OneShotEffect { + + SaheeliSublimeArtificerEffect() { + super(Outcome.Benefit); + staticText = "Target artifact you control becomes a copy of another target artifact or creature you control" + + " until end of turn, except it's an artifact in addition to its other types."; + } + + private SaheeliSublimeArtificerEffect(final SaheeliSublimeArtificerEffect effect) { + super(effect); + } + + @Override + public SaheeliSublimeArtificerEffect copy() { + return new SaheeliSublimeArtificerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent copyTo = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (copyTo != null) { + Permanent copyFrom = game.getPermanentOrLKIBattlefield(source.getTargets().get(1).getFirstTarget()); + if (copyFrom != null) { + game.copyPermanent(Duration.EndOfTurn, copyFrom, copyTo.getId(), source, new EmptyApplyToPermanent()); + ContinuousEffect effect = new AddCardTypeTargetEffect(Duration.EndOfTurn, CardType.ARTIFACT); + effect.setTargetPointer(new FixedTarget(copyTo, game)); + game.addEffect(effect, source); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SaheelisSilverwing.java b/Mage.Sets/src/mage/cards/s/SaheelisSilverwing.java new file mode 100644 index 0000000000..57cd9700d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaheelisSilverwing.java @@ -0,0 +1,47 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.LookLibraryTopCardTargetPlayerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SaheelisSilverwing extends CardImpl { + + public SaheelisSilverwing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); + + this.subtype.add(SubType.DRAKE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Saheeli's Silverwing enters the battlefield, look at the top card of target opponent's library. + Ability ability = new EntersBattlefieldTriggeredAbility( + new LookLibraryTopCardTargetPlayerEffect().setText("look at the top card of target opponent's library") + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private SaheelisSilverwing(final SaheelisSilverwing card) { + super(card); + } + + @Override + public SaheelisSilverwing copy() { + return new SaheelisSilverwing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SamutTyrantSmasher.java b/Mage.Sets/src/mage/cards/s/SamutTyrantSmasher.java index 8340be7acc..8468e13ebb 100644 --- a/Mage.Sets/src/mage/cards/s/SamutTyrantSmasher.java +++ b/Mage.Sets/src/mage/cards/s/SamutTyrantSmasher.java @@ -1,5 +1,6 @@ package mage.cards.s; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; @@ -18,8 +19,6 @@ import mage.constants.SuperType; import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; -import java.util.UUID; - /** * @author TheElk801 */ @@ -44,7 +43,7 @@ public final class SamutTyrantSmasher extends CardImpl { ).setText("target creature gets +2/+1"), -1); ability.addEffect(new GainAbilityTargetEffect( HasteAbility.getInstance(), Duration.EndOfTurn - ).setText("and gains haste until end of turn.")); + ).setText("and gains haste until end of turn")); ability.addEffect(new ScryEffect(1)); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SamutsSprint.java b/Mage.Sets/src/mage/cards/s/SamutsSprint.java index 28862ab573..1d631dd602 100644 --- a/Mage.Sets/src/mage/cards/s/SamutsSprint.java +++ b/Mage.Sets/src/mage/cards/s/SamutsSprint.java @@ -1,5 +1,6 @@ package mage.cards.s; +import java.util.UUID; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.keyword.ScryEffect; @@ -10,8 +11,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetCreaturePermanent; -import java.util.UUID; - /** * @author TheElk801 */ @@ -26,7 +25,7 @@ public final class SamutsSprint extends CardImpl { ).setText("target creature gets +2/+1")); this.getSpellAbility().addEffect(new GainAbilityTargetEffect( HasteAbility.getInstance(), Duration.EndOfTurn - ).setText("and gains haste until end of turn.")); + ).setText("and gains haste until end of turn")); this.getSpellAbility().addEffect(new ScryEffect(1)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java b/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java new file mode 100644 index 0000000000..aa6a06f345 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SarkhanTheMasterless.java @@ -0,0 +1,165 @@ +package mage.cards.s; + +import mage.MageObjectReference; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +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.game.permanent.token.DragonToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SarkhanTheMasterless extends CardImpl { + + public SarkhanTheMasterless(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{R}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SARKHAN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Whenever a creature attacks you or a planeswalker you control, each Dragon you control deals 1 damage to that creature. + this.addAbility(new AttacksAllTriggeredAbility( + new SarkhanTheMasterlessDamageEffect(), + false, StaticFilters.FILTER_PERMANENT_A_CREATURE, + SetTargetPointer.PERMANENT, true + )); + + // +1: Until end of turn, each planeswalker you control becomes a 4/4 red Dragon creature and gains flying. + this.addAbility(new LoyaltyAbility(new SarkhanTheMasterlessBecomeDragonEffect(), 1)); + + // -3: Create a 4/4 red Dragon creature token with flying. + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DragonToken()), -3)); + } + + private SarkhanTheMasterless(final SarkhanTheMasterless card) { + super(card); + } + + @Override + public SarkhanTheMasterless copy() { + return new SarkhanTheMasterless(this); + } +} + +class SarkhanTheMasterlessDamageEffect extends OneShotEffect { + + SarkhanTheMasterlessDamageEffect() { + super(Outcome.Benefit); + staticText = "each Dragon you control deals 1 damage to that creature."; + } + + private SarkhanTheMasterlessDamageEffect(final SarkhanTheMasterlessDamageEffect effect) { + super(effect); + } + + @Override + public SarkhanTheMasterlessDamageEffect copy() { + return new SarkhanTheMasterlessDamageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + if (creature == null) { + return false; + } + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { + if (permanent != null && permanent.hasSubtype(SubType.DRAGON, game)) { + creature.damage(1, permanent.getId(), game); + } + } + return true; + } +} + +class SarkhanTheMasterlessBecomeDragonEffect extends ContinuousEffectImpl { + + SarkhanTheMasterlessBecomeDragonEffect() { + super(Duration.EndOfTurn, Outcome.BecomeCreature); + staticText = "Until end of turn, each planeswalker you control becomes a 4/4 red Dragon creature and gains flying."; + } + + private SarkhanTheMasterlessBecomeDragonEffect(final SarkhanTheMasterlessBecomeDragonEffect effect) { + super(effect); + } + + @Override + public SarkhanTheMasterlessBecomeDragonEffect copy() { + return new SarkhanTheMasterlessBecomeDragonEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { + if (permanent != null && permanent.isPlaneswalker()) { + affectedObjectList.add(new MageObjectReference(permanent, game)); + } + } + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + boolean flag = false; + for (MageObjectReference mor : affectedObjectList) { + Permanent permanent = mor.getPermanent(game); + if (permanent == null) { + continue; + } + flag = true; + switch (layer) { + case TypeChangingEffects_4: + if (sublayer == SubLayer.NA) { + permanent.getCardType().clear(); + permanent.addCardType(CardType.CREATURE); + permanent.getSubtype(game).clear(); + permanent.getSubtype(game).add(SubType.DRAGON); + permanent.getSuperType().clear(); + } + break; + case ColorChangingEffects_5: + permanent.getColor(game).setColor(ObjectColor.RED); + break; + case AbilityAddingRemovingEffects_6: + if (sublayer == SubLayer.NA) { + permanent.addAbility(FlyingAbility.getInstance(), source.getSourceId(), game); + } + break; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + permanent.getPower().setValue(4); + permanent.getToughness().setValue(4); + } + } + } + return flag; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.PTChangingEffects_7 + || layer == Layer.AbilityAddingRemovingEffects_6 + || layer == Layer.ColorChangingEffects_5 + || layer == Layer.TypeChangingEffects_4; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SarkhansCatharsis.java b/Mage.Sets/src/mage/cards/s/SarkhansCatharsis.java new file mode 100644 index 0000000000..85db301710 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SarkhansCatharsis.java @@ -0,0 +1,32 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetPlayerOrPlaneswalker; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SarkhansCatharsis extends CardImpl { + + public SarkhansCatharsis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Sarkhan's Catharsis deals 5 damage to target player or planeswalker. + this.getSpellAbility().addEffect(new DamageTargetEffect(5)); + this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); + } + + private SarkhansCatharsis(final SarkhansCatharsis card) { + super(card); + } + + @Override + public SarkhansCatharsis copy() { + return new SarkhansCatharsis(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Scapeshift.java b/Mage.Sets/src/mage/cards/s/Scapeshift.java index 473044dfbd..b4eb29e6ee 100644 --- a/Mage.Sets/src/mage/cards/s/Scapeshift.java +++ b/Mage.Sets/src/mage/cards/s/Scapeshift.java @@ -75,7 +75,7 @@ class ScapeshiftEffect extends OneShotEffect { } } TargetCardInLibrary target = new TargetCardInLibrary(amount, new FilterLandCard("lands")); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/s/ScionOfTheUrDragon.java b/Mage.Sets/src/mage/cards/s/ScionOfTheUrDragon.java index ea6a90a501..5fccd4eb92 100644 --- a/Mage.Sets/src/mage/cards/s/ScionOfTheUrDragon.java +++ b/Mage.Sets/src/mage/cards/s/ScionOfTheUrDragon.java @@ -79,7 +79,7 @@ class ScionOfTheUrDragonEffect extends SearchEffect { Player player = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && sourcePermanent != null) { - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().getCard(cardId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/s/SecretSalvage.java b/Mage.Sets/src/mage/cards/s/SecretSalvage.java index 04707c8c43..50495f98a5 100644 --- a/Mage.Sets/src/mage/cards/s/SecretSalvage.java +++ b/Mage.Sets/src/mage/cards/s/SecretSalvage.java @@ -76,7 +76,7 @@ class SecretSalvageEffect extends OneShotEffect { String nameToSearch = targetCard.isSplitCard() ? ((SplitCard) targetCard).getLeftHalfCard().getName() : targetCard.getName(); nameFilter.add(new NamePredicate(nameToSearch)); TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, nameFilter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/s/SecretsOfTheGoldenCity.java b/Mage.Sets/src/mage/cards/s/SecretsOfTheGoldenCity.java index 705800cd94..02245199ab 100644 --- a/Mage.Sets/src/mage/cards/s/SecretsOfTheGoldenCity.java +++ b/Mage.Sets/src/mage/cards/s/SecretsOfTheGoldenCity.java @@ -5,6 +5,7 @@ import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.keyword.AscendEffect; import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.PermanentsYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -21,6 +22,8 @@ public final class SecretsOfTheGoldenCity extends CardImpl { // Ascend this.getSpellAbility().addEffect(new AscendEffect()); + this.getSpellAbility().addHint(CitysBlessingHint.instance); + this.getSpellAbility().addHint(PermanentsYouControlHint.instance); // Draw two cards. If you have the city's blessing, draw three cards instead. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( @@ -28,7 +31,6 @@ public final class SecretsOfTheGoldenCity extends CardImpl { new DrawCardSourceControllerEffect(2), CitysBlessingCondition.instance, "Draw two cards. If you have the city's blessing, draw three cards instead")); - this.getSpellAbility().addHint(CitysBlessingHint.instance); } public SecretsOfTheGoldenCity(final SecretsOfTheGoldenCity card) { diff --git a/Mage.Sets/src/mage/cards/s/SelectiveMemory.java b/Mage.Sets/src/mage/cards/s/SelectiveMemory.java index cd753fec8b..83966eb414 100644 --- a/Mage.Sets/src/mage/cards/s/SelectiveMemory.java +++ b/Mage.Sets/src/mage/cards/s/SelectiveMemory.java @@ -59,7 +59,7 @@ class SelectiveMemoryEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, new FilterNonlandCard()); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { for (UUID targetId : target.getTargets()) { Card card = player.getLibrary().remove(targetId, game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/s/SettleTheWreckage.java b/Mage.Sets/src/mage/cards/s/SettleTheWreckage.java index 4a2a86accd..8156a6bc70 100644 --- a/Mage.Sets/src/mage/cards/s/SettleTheWreckage.java +++ b/Mage.Sets/src/mage/cards/s/SettleTheWreckage.java @@ -80,7 +80,7 @@ class SettleTheWreckageEffect extends OneShotEffect { } controller.moveCards(toExile, Zone.EXILED, source, game); TargetCardInLibrary target = new TargetCardInLibrary(0, attackers, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.chooseUse(Outcome.Benefit, "Search for up to " + attackers + " basic land" + ((attackers == 1) ? "" : "s") + "?", source, game) && player.searchLibrary(target, game)) { + if (player.chooseUse(Outcome.Benefit, "Search for up to " + attackers + " basic land" + ((attackers == 1) ? "" : "s") + "?", source, game) && player.searchLibrary(target, source, game)) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); player.shuffleLibrary(source, game); } diff --git a/Mage.Sets/src/mage/cards/s/ShardConvergence.java b/Mage.Sets/src/mage/cards/s/ShardConvergence.java index 704e64c710..b0ed89c4df 100644 --- a/Mage.Sets/src/mage/cards/s/ShardConvergence.java +++ b/Mage.Sets/src/mage/cards/s/ShardConvergence.java @@ -77,7 +77,7 @@ class ShardConvergenceEffect extends OneShotEffect { FilterLandCard filter = new FilterLandCard(subtype); filter.add(new SubtypePredicate(SubType.byDescription(subtype))); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { Card card = player.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { card.moveToZone(Zone.HAND, source.getSourceId(), game, false); diff --git a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java index b10e036e41..a06d58fe98 100644 --- a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java +++ b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java @@ -125,7 +125,7 @@ class ShimianSpecterEffect extends OneShotEffect { // If the player has no nonland cards in their hand, you can still search that player's library and have him or her shuffle it. if (chosenCard != null || controller.chooseUse(outcome, "Search library anyway?", source, game)) { TargetCardInLibrary targetCardsLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCards); - controller.searchLibrary(targetCardsLibrary, game, targetPlayer.getId()); + controller.searchLibrary(targetCardsLibrary, source, game, targetPlayer.getId()); for (UUID cardId : targetCardsLibrary.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/s/Shriekdiver.java b/Mage.Sets/src/mage/cards/s/Shriekdiver.java new file mode 100644 index 0000000000..0c975762c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Shriekdiver.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Shriekdiver extends CardImpl { + + public Shriekdiver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // {1}: Shriekdiver gains haste until end of turn. + this.addAbility(new SimpleActivatedAbility(new GainAbilitySourceEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ), new GenericManaCost(1))); + } + + private Shriekdiver(final Shriekdiver card) { + super(card); + } + + @Override + public Shriekdiver copy() { + return new Shriekdiver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SignalTheClans.java b/Mage.Sets/src/mage/cards/s/SignalTheClans.java index bcd225c68e..44fc5b18e5 100644 --- a/Mage.Sets/src/mage/cards/s/SignalTheClans.java +++ b/Mage.Sets/src/mage/cards/s/SignalTheClans.java @@ -13,7 +13,6 @@ import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInLibrary; -import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -66,7 +65,7 @@ class SignalTheClansEffect extends SearchEffect { return false; } //Search your library for three creature cards - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId: target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/s/SilentSubmersible.java b/Mage.Sets/src/mage/cards/s/SilentSubmersible.java new file mode 100644 index 0000000000..5539dff893 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilentSubmersible.java @@ -0,0 +1,43 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SilentSubmersible extends CardImpl { + + public SilentSubmersible(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}{U}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever Silent Submarine deals combat damage to a player or planeswalker, draw a card. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + ).setOrPlaneswalker(true)); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private SilentSubmersible(final SilentSubmersible card) { + super(card); + } + + @Override + public SilentSubmersible copy() { + return new SilentSubmersible(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SingleCombat.java b/Mage.Sets/src/mage/cards/s/SingleCombat.java index e352cccdb4..d4c235beb1 100644 --- a/Mage.Sets/src/mage/cards/s/SingleCombat.java +++ b/Mage.Sets/src/mage/cards/s/SingleCombat.java @@ -97,7 +97,7 @@ class SingleCombatEffect extends OneShotEffect { class SingleCombatRestrictionEffect extends ContinuousRuleModifyingEffectImpl { SingleCombatRestrictionEffect() { - super(Duration.UntilYourNextTurn, Outcome.Neutral); + super(Duration.UntilEndOfYourNextTurn, Outcome.Neutral); staticText = "Players can't cast creature or planeswalker spells until the end of your next turn."; } diff --git a/Mage.Sets/src/mage/cards/s/SkyTheaterStrix.java b/Mage.Sets/src/mage/cards/s/SkyTheaterStrix.java new file mode 100644 index 0000000000..518481cf59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SkyTheaterStrix.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +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.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SkyTheaterStrix extends CardImpl { + + public SkyTheaterStrix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast a noncreature spell, Sky Theater Strix gets +1/+0 until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new BoostSourceEffect(1, 0, Duration.EndOfTurn), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + } + + private SkyTheaterStrix(final SkyTheaterStrix card) { + super(card); + } + + @Override + public SkyTheaterStrix copy() { + return new SkyTheaterStrix(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java b/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java index e57d02047a..da4ce8dc16 100644 --- a/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java +++ b/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java @@ -91,7 +91,7 @@ class SkyshipWeatherlightEffect extends SearchEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); if (!target.getTargets().isEmpty()) { for (UUID cardID : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/s/Snarespinner.java b/Mage.Sets/src/mage/cards/s/Snarespinner.java new file mode 100644 index 0000000000..d738467dd8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Snarespinner.java @@ -0,0 +1,78 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ReachAbility; +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.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Snarespinner extends CardImpl { + + public Snarespinner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.SPIDER); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Snarespinner blocks a creature with flying, Snarespinner gets +2/+0 until end of turn. + this.addAbility(new SnarespinnerTriggeredAbility()); + } + + private Snarespinner(final Snarespinner card) { + super(card); + } + + @Override + public Snarespinner copy() { + return new Snarespinner(this); + } +} + +class SnarespinnerTriggeredAbility extends TriggeredAbilityImpl { + + SnarespinnerTriggeredAbility() { + super(Zone.BATTLEFIELD, new BoostSourceEffect(2, 0, Duration.EndOfTurn), false); + } + + private SnarespinnerTriggeredAbility(final SnarespinnerTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getSourceId().equals(this.getSourceId()) + && game.getPermanent(event.getTargetId()).getAbilities().containsKey(FlyingAbility.getInstance().getId()); + } + + @Override + public String getRule() { + return "Whenever {this} blocks a creature with flying, {} gets +2/+0 until end of turn"; + } + + @Override + public SnarespinnerTriggeredAbility copy() { + return new SnarespinnerTriggeredAbility(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SolarBlaze.java b/Mage.Sets/src/mage/cards/s/SolarBlaze.java new file mode 100644 index 0000000000..eeeca91888 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SolarBlaze.java @@ -0,0 +1,62 @@ +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SolarBlaze extends CardImpl { + + public SolarBlaze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{W}"); + + // Each creature deals damage to itself equal to its power. + this.getSpellAbility().addEffect(new SolarBlazeEffect()); + } + + private SolarBlaze(final SolarBlaze card) { + super(card); + } + + @Override + public SolarBlaze copy() { + return new SolarBlaze(this); + } +} + +class SolarBlazeEffect extends OneShotEffect { + + SolarBlazeEffect() { + super(Outcome.Benefit); + staticText = "Each creature deals damage to itself equal to its power."; + } + + private SolarBlazeEffect(final SolarBlazeEffect effect) { + super(effect); + } + + @Override + public SolarBlazeEffect copy() { + return new SolarBlazeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game) + ) { + permanent.damage(permanent.getPower().getValue(), permanent.getId(), game); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SoldeviSentry.java b/Mage.Sets/src/mage/cards/s/SoldeviSentry.java new file mode 100644 index 0000000000..14d2fdb476 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SoldeviSentry.java @@ -0,0 +1,68 @@ + +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.RegenerateSourceEffect; +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.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +/** + * + * @author Ketsuban + */ +public final class SoldeviSentry extends CardImpl { + + public SoldeviSentry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT, CardType.CREATURE }, "{1}"); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // 1: Choose target opponent. Regenerate Soldevi Sentry. When it regenerates + // this way, that player may draw a card. + Ability ability = new SimpleActivatedAbility(new SoldeviSentryEffect(), new GenericManaCost(1)); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + public SoldeviSentry(final SoldeviSentry card) { + super(card); + } + + @Override + public SoldeviSentry copy() { + return new SoldeviSentry(this); + } +} + +class SoldeviSentryEffect extends RegenerateSourceEffect { + + @Override + public boolean apply(Game game, Ability source) { + //20110204 - 701.11 + Player opponent = game.getPlayer(source.getFirstTarget()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null && permanent.regenerate(this.getId(), game)) { + if (opponent != null) { + if (opponent.chooseUse(Outcome.DrawCard, "Draw a card?", source, game)) { + opponent.drawCards(1, game); + } + } + this.used = true; + return true; + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/s/Solemnity.java b/Mage.Sets/src/mage/cards/s/Solemnity.java index 0ce74644da..858d9c85f2 100644 --- a/Mage.Sets/src/mage/cards/s/Solemnity.java +++ b/Mage.Sets/src/mage/cards/s/Solemnity.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -21,8 +19,9 @@ import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author spjspj */ public final class Solemnity extends CardImpl { @@ -70,7 +69,7 @@ class SolemnityEffect extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.ADD_COUNTER; + return event.getType() == EventType.ADD_COUNTERS; } @Override @@ -113,7 +112,7 @@ class SolemnityEffect2 extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.ADD_COUNTER || event.getType() == EventType.ADD_COUNTERS; + return event.getType() == EventType.ADD_COUNTERS; } @Override @@ -124,21 +123,13 @@ class SolemnityEffect2 extends ReplacementEffectImpl { Permanent permanent3 = game.getPermanentEntering(event.getTargetId()); if (object instanceof Permanent) { - if (filter.match((Permanent) object, game)) { - return true; - } + return filter.match((Permanent) object, game); } else if (permanent != null) { - if (filter.match(permanent, game)) { - return true; - } + return filter.match(permanent, game); } else if (permanent2 != null) { - if (filter.match(permanent2, game)) { - return true; - } + return filter.match(permanent2, game); } else if (permanent3 != null) { - if (filter.match(permanent3, game)) { - return true; - } + return filter.match(permanent3, game); } return false; diff --git a/Mage.Sets/src/mage/cards/s/SoulDiviner.java b/Mage.Sets/src/mage/cards/s/SoulDiviner.java new file mode 100644 index 0000000000..0b02ccf33d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SoulDiviner.java @@ -0,0 +1,62 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCounterCost; +import mage.abilities.costs.common.TapSourceCost; +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.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SoulDiviner extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("an artifact, creature, land, or planeswalker you control"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.LAND), + new CardTypePredicate(CardType.PLANESWALKER) + )); + } + + public SoulDiviner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {T}, Remove a counter from an artifact, creature, land, or planeswalker you control: Draw a card. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new TapSourceCost() + ); + ability.addCost(new RemoveCounterCost(new TargetPermanent(filter))); + this.addAbility(ability); + } + + private SoulDiviner(final SoulDiviner card) { + super(card); + } + + @Override + public SoulDiviner copy() { + return new SoulDiviner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Soulflayer.java b/Mage.Sets/src/mage/cards/s/Soulflayer.java index 62a492f357..2195981e79 100644 --- a/Mage.Sets/src/mage/cards/s/Soulflayer.java +++ b/Mage.Sets/src/mage/cards/s/Soulflayer.java @@ -1,44 +1,26 @@ - package mage.cards.s; -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.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.continuous.SourceEffect; -import mage.abilities.keyword.DeathtouchAbility; -import mage.abilities.keyword.DelveAbility; -import mage.abilities.keyword.DoubleStrikeAbility; -import mage.abilities.keyword.FirstStrikeAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.abilities.keyword.HasteAbility; -import mage.abilities.keyword.HexproofAbility; -import mage.abilities.keyword.IndestructibleAbility; -import mage.abilities.keyword.LifelinkAbility; -import mage.abilities.keyword.ReachAbility; -import mage.abilities.keyword.TrampleAbility; -import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.*; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.Cards; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class Soulflayer extends CardImpl { @@ -82,7 +64,9 @@ class SoulflayerEffect extends ContinuousEffectImpl implements SourceEffect { super(effect); if (effect.abilitiesToAdd != null) { this.abilitiesToAdd = new HashSet<>(); - this.abilitiesToAdd.addAll(effect.abilitiesToAdd); + for (Ability a : effect.abilitiesToAdd) { + this.abilitiesToAdd.add(a.copy()); + } } this.objectReference = effect.objectReference; } @@ -96,6 +80,7 @@ class SoulflayerEffect extends ContinuousEffectImpl implements SourceEffect { public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent != null) { + // one time abilities collect if (objectReference == null || !objectReference.refersTo(permanent, game)) { abilitiesToAdd = new HashSet<>(); this.objectReference = new MageObjectReference(permanent, game); @@ -144,6 +129,8 @@ class SoulflayerEffect extends ContinuousEffectImpl implements SourceEffect { } } } + + // all time abilities apply for (Ability ability : abilitiesToAdd) { permanent.addAbility(ability, source.getSourceId(), game); } diff --git a/Mage.Sets/src/mage/cards/s/Soulshriek.java b/Mage.Sets/src/mage/cards/s/Soulshriek.java new file mode 100644 index 0000000000..93dc244d8a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Soulshriek.java @@ -0,0 +1,83 @@ +package mage.cards.s; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +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.SacrificeTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +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.filter.FilterCard; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/* + * + * @author Ketsuban + */ +public class Soulshriek extends CardImpl { + + protected static final FilterCard filterCard = new FilterCard("creature cards"); + + static { + filterCard.add(new CardTypePredicate(CardType.CREATURE)); + } + + public Soulshriek(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}"); + + // Target creature you control gets +X/+0 until end of turn, where X is the number of creature cards in your graveyard. Sacrifice that creature at the beginning of the next end step. + this.getSpellAbility().addEffect(new SoulshriekEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + } + + @Override + public Card copy() { + return null; + } +} + +class SoulshriekEffect extends OneShotEffect { + + public SoulshriekEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Target creature you control gets +X/+0 until end of turn, where X is the number of creature cards in your graveyard. Sacrifice that creature at the beginning of the next end step"; + } + + public SoulshriekEffect(final SoulshriekEffect effect) { + super(effect); + } + + @Override + public SoulshriekEffect copy() { + return new SoulshriekEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + ContinuousEffect boost = new BoostTargetEffect(new CardsInControllerGraveyardCount(Soulshriek.filterCard), new StaticValue(0), Duration.EndOfTurn); + boost.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(boost, source); + Effect sacrifice = new SacrificeTargetEffect("Sacrifice that creature at the beginning of the next end step", source.getControllerId()); + sacrifice.setTargetPointer(new FixedTarget(permanent, game)); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrifice), source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SovereignsOfLostAlara.java b/Mage.Sets/src/mage/cards/s/SovereignsOfLostAlara.java index bf9a5c6d90..14990a29f0 100644 --- a/Mage.Sets/src/mage/cards/s/SovereignsOfLostAlara.java +++ b/Mage.Sets/src/mage/cards/s/SovereignsOfLostAlara.java @@ -114,7 +114,7 @@ class SovereignsOfLostAlaraEffect extends OneShotEffect { if (controller.chooseUse(Outcome.Benefit, "Do you want to search your library?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(filter); target.setNotTarget(true); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (target.getFirstTarget() != null) { Card aura = game.getCard(target.getFirstTarget()); game.getState().setValue("attachTo:" + aura.getId(), attackingCreature); diff --git a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java index 18d91f4757..a277ee931e 100644 --- a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java +++ b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java @@ -8,12 +8,12 @@ import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; 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; @@ -124,7 +124,7 @@ class SowerOfDiscordTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGE_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/s/SparkDouble.java b/Mage.Sets/src/mage/cards/s/SparkDouble.java new file mode 100644 index 0000000000..b036665e2f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SparkDouble.java @@ -0,0 +1,105 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyPermanentEffect; +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.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.functions.ApplyToPermanent; + +import java.util.UUID; + +/** + * @author JayDi85 + */ +public final class SparkDouble extends CardImpl { + + private static FilterPermanent filter = new FilterControlledPermanent("a creature or planeswalker you control"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.PLANESWALKER))); + } + + public SparkDouble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + this.subtype.add(SubType.ILLUSION); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // You may have Spark Double enter the battlefield as a copy of a creature or planeswalker you control, + // except it enters with an additional +1/+1 counter on it if it’s a creature, + // it enters with an additional loyalty counter on it if it’s a planeswalker, and it isn’t legendary if that permanent is legendary. + Effect effect = new CopyPermanentEffect(filter, new SparkDoubleExceptEffectsApplyerToPermanent()); + effect.setText("as a copy of a creature or planeswalker you control, " + + "except it enters with an additional +1/+1 counter on it if it’s a creature, " + + "it enters with an additional loyalty counter on it if it’s a planeswalker, and it isn’t legendary if that permanent is legendary."); + EntersBattlefieldAbility ability = new EntersBattlefieldAbility(effect, true); + this.addAbility(ability); + } + + public SparkDouble(final SparkDouble card) { + super(card); + } + + @Override + public SparkDouble copy() { + return new SparkDouble(this); + } +} + +class SparkDoubleExceptEffectsApplyerToPermanent extends ApplyToPermanent { + + @Override + public boolean apply(Game game, Permanent copyFromBlueprint, Ability source, UUID copyToObjectId) { + return apply(game, (MageObject) copyFromBlueprint, source, copyToObjectId); + } + + @Override + public boolean apply(Game game, MageObject copyFromBlueprint, Ability source, UUID copyToObjectId) { + Permanent destCard = game.getPermanentEntering(copyToObjectId); + if (destCard == null) { + return false; + } + + // it isn’t legendary if that permanent is legendary + copyFromBlueprint.getSuperType().remove(SuperType.LEGENDARY); + + // TODO: Blood Moon problem, can't apply on type changing effects (same as TeferisTimeTwist) + // see https://magic.wizards.com/en/articles/archive/feature/war-spark-release-notes-2019-04-19 + // If the copied permanent is affected by a type-changing effect, Spark Double may enter the battlefield with + // different permanent types than the copied permanent currently has. Use the characteristics of Spark Double as + // it enters the battlefield, not of the copied permanent, to determine whether it enters with an additional + // counter on it. Notably, if Spark Double copies a Gideon planeswalker that's a creature because its loyalty + // ability caused it to become a planeswalker creature, Spark Double enters as a noncreature planeswalker and + // doesn't get a +1/+1 counter. On the other hand, if Spark Double copies Gideon Blackblade during your turn, + // Spark Double enters as a planeswalker creature and gets both kinds of counters. + + // enters with an additional +1/+1 counter on it if it’s a creature + if (copyFromBlueprint.isCreature()) { + destCard.addCounters(CounterType.P1P1.createInstance(), source, game); + } + + // enters with an additional loyalty counter on it if it’s a planeswalker + if (copyFromBlueprint.isPlaneswalker()) { + destCard.addCounters(CounterType.LOYALTY.createInstance(), source, game); + } + + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/s/SparkHarvest.java b/Mage.Sets/src/mage/cards/s/SparkHarvest.java new file mode 100644 index 0000000000..39c55762c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SparkHarvest.java @@ -0,0 +1,42 @@ +package mage.cards.s; + +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreatureOrPlaneswalker; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SparkHarvest extends CardImpl { + + public SparkHarvest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // As an additional cost to cast this spell, sacrifice a creature or pay {3}{B}. + this.getSpellAbility().addCost(new OrCost( + new SacrificeTargetCost(new TargetControlledCreaturePermanent()), + new ManaCostsImpl("{3}{B}"), "sacrifice a creature or pay {3}{B}" + )); + + // Destroy target creature or planeswalker. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); + } + + private SparkHarvest(final SparkHarvest card) { + super(card); + } + + @Override + public SparkHarvest copy() { + return new SparkHarvest(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SparkReaper.java b/Mage.Sets/src/mage/cards/s/SparkReaper.java new file mode 100644 index 0000000000..da57ae0a3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SparkReaper.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SparkReaper extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent("a creature or planeswalker"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.PLANESWALKER) + )); + } + + public SparkReaper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {3}, Sacrifice a creature or planeswalker: You gain 1 life and draw a card. + Ability ability = new SimpleActivatedAbility(new GainLifeEffect(1), new GenericManaCost(3)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); + this.addAbility(ability); + } + + private SparkReaper(final SparkReaper card) { + super(card); + } + + @Override + public SparkReaper copy() { + return new SparkReaper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpellgorgerWeird.java b/Mage.Sets/src/mage/cards/s/SpellgorgerWeird.java index 9b0625f253..263a47f6af 100644 --- a/Mage.Sets/src/mage/cards/s/SpellgorgerWeird.java +++ b/Mage.Sets/src/mage/cards/s/SpellgorgerWeird.java @@ -1,5 +1,6 @@ package mage.cards.s; +import java.util.UUID; import mage.MageInt; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -10,8 +11,6 @@ import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; -import java.util.UUID; - /** * @author TheElk801 */ @@ -27,7 +26,7 @@ public final class SpellgorgerWeird extends CardImpl { // Whenever you cast a noncreature spell, put a +1/+1 counter on Spellgorger Weird. this.addAbility(new SpellCastControllerTriggeredAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), - StaticFilters.FILTER_SPELL_NON_CREATURE, false + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false )); } diff --git a/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java b/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java index f276a9393e..61f389621c 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java +++ b/Mage.Sets/src/mage/cards/s/SphinxAmbassador.java @@ -74,7 +74,7 @@ class SphinxAmbassadorEffect extends OneShotEffect { Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (controller != null && targetPlayer != null && sourcePermanent != null) { TargetCardInLibrary target = new TargetCardInLibrary(); - controller.searchLibrary(target, game, targetPlayer.getId()); + controller.searchLibrary(target, source, game, targetPlayer.getId()); Card card = game.getCard(target.getFirstTarget()); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/s/SphinxOfJwarIsle.java b/Mage.Sets/src/mage/cards/s/SphinxOfJwarIsle.java index ca70c11ed4..cbde695d7f 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxOfJwarIsle.java +++ b/Mage.Sets/src/mage/cards/s/SphinxOfJwarIsle.java @@ -1,34 +1,24 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.ActivatedAbilityImpl; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ShroudAbility; -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.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.players.Player; + +import java.util.UUID; /** - * * @author North */ public final class SphinxOfJwarIsle extends CardImpl { public SphinxOfJwarIsle(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); this.subtype.add(SubType.SPHINX); this.power = new MageInt(5); @@ -36,12 +26,10 @@ public final class SphinxOfJwarIsle extends CardImpl { this.addAbility(FlyingAbility.getInstance()); this.addAbility(ShroudAbility.getInstance()); - // TODO: this should be a static ability - this.addAbility(new SphinxOfJwarIsleLookAbility()); - + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); } - public SphinxOfJwarIsle(final SphinxOfJwarIsle card) { + private SphinxOfJwarIsle(final SphinxOfJwarIsle card) { super(card); } @@ -50,56 +38,3 @@ public final class SphinxOfJwarIsle extends CardImpl { return new SphinxOfJwarIsle(this); } } - -class SphinxOfJwarIsleLookAbility extends ActivatedAbilityImpl { - - public SphinxOfJwarIsleLookAbility() { - super(Zone.BATTLEFIELD, new SphinxOfJwarIsleEffect(), new GenericManaCost(0)); - this.usesStack = false; - } - - public SphinxOfJwarIsleLookAbility(SphinxOfJwarIsleLookAbility ability) { - super(ability); - } - - @Override - public SphinxOfJwarIsleLookAbility copy() { - return new SphinxOfJwarIsleLookAbility(this); - } - -} - -class SphinxOfJwarIsleEffect extends OneShotEffect { - - public SphinxOfJwarIsleEffect() { - super(Outcome.Neutral); - this.staticText = "You may look at the top card of your library any time"; - } - - public SphinxOfJwarIsleEffect(final SphinxOfJwarIsleEffect effect) { - super(effect); - } - - @Override - public SphinxOfJwarIsleEffect copy() { - return new SphinxOfJwarIsleEffect(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) { - Cards cards = new CardsImpl(card); - player.lookAtCards("Sphinx of Jwar Isle", cards, game); - } else { - return false; - } - - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SpreadTheSickness.java b/Mage.Sets/src/mage/cards/s/SpreadTheSickness.java index 1b56e195a4..eb97e23df1 100644 --- a/Mage.Sets/src/mage/cards/s/SpreadTheSickness.java +++ b/Mage.Sets/src/mage/cards/s/SpreadTheSickness.java @@ -1,8 +1,5 @@ - - package mage.cards.s; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.cards.CardImpl; @@ -10,21 +7,23 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author Loki */ public final class SpreadTheSickness extends CardImpl { - public SpreadTheSickness (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{B}"); + public SpreadTheSickness(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); + // Destroy target creature, then proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new ProliferateEffect()); + this.getSpellAbility().addEffect(new ProliferateEffect().concatBy(", then")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } - public SpreadTheSickness (final SpreadTheSickness card) { + public SpreadTheSickness(final SpreadTheSickness card) { super(card); } diff --git a/Mage.Sets/src/mage/cards/s/SteadyAim.java b/Mage.Sets/src/mage/cards/s/SteadyAim.java new file mode 100644 index 0000000000..ba8a85edfe --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteadyAim.java @@ -0,0 +1,44 @@ +package mage.cards.s; + +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SteadyAim extends CardImpl { + + public SteadyAim(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Untap target creature. It gets +1/+4 and gains reach until end of turn. + this.getSpellAbility().addEffect(new UntapTargetEffect()); + this.getSpellAbility().addEffect(new BoostTargetEffect( + 1, 4, Duration.EndOfTurn + ).setText("It gets +1/+4")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + ReachAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains reach until end of turn")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private SteadyAim(final SteadyAim card) { + super(card); + } + + @Override + public SteadyAim copy() { + return new SteadyAim(this); + } +} +// I'm labor ready Rhode Scholar for the dollar +// Work for mines pay me by the hour \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/StoneTongueBasilisk.java b/Mage.Sets/src/mage/cards/s/StoneTongueBasilisk.java index d26774fce1..a64a30f745 100644 --- a/Mage.Sets/src/mage/cards/s/StoneTongueBasilisk.java +++ b/Mage.Sets/src/mage/cards/s/StoneTongueBasilisk.java @@ -1,14 +1,12 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToACreatureTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.condition.common.CardsInControllerGraveCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRequirementEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DestroyTargetEffect; @@ -20,14 +18,15 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import java.util.UUID; + /** - * * @author fireshoes */ public final class StoneTongueBasilisk extends CardImpl { public StoneTongueBasilisk(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}{G}"); this.subtype.add(SubType.BASILISK); this.power = new MageInt(4); this.toughness = new MageInt(5); @@ -39,9 +38,11 @@ public final class StoneTongueBasilisk extends CardImpl { this.addAbility(new DealsCombatDamageToACreatureTriggeredAbility(effect, false, true)); // Threshold - As long as seven or more cards are in your graveyard, all creatures able to block Stone-Tongue Basilisk do so. - Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new MustBeBlockedByAllSourceEffect(), new CardsInControllerGraveCondition(7), - "As long as seven or more cards are in your graveyard, all creatures able to block {this} do so")); + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalRequirementEffect( + new MustBeBlockedByAllSourceEffect(), + new CardsInControllerGraveCondition(7), + "As long as seven or more cards are in your graveyard, all creatures able to block {this} do so" + )); ability.setAbilityWord(AbilityWord.THRESHOLD); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/StonehewerGiant.java b/Mage.Sets/src/mage/cards/s/StonehewerGiant.java index 8fb3a3d39c..70de8c460b 100644 --- a/Mage.Sets/src/mage/cards/s/StonehewerGiant.java +++ b/Mage.Sets/src/mage/cards/s/StonehewerGiant.java @@ -88,7 +88,7 @@ class StonehewerGiantEffect extends OneShotEffect { FilterCard filter = new FilterCard("Equipment"); filter.add(new SubtypePredicate(SubType.EQUIPMENT)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/s/StormTheCitadel.java b/Mage.Sets/src/mage/cards/s/StormTheCitadel.java new file mode 100644 index 0000000000..0b7f2266d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StormTheCitadel.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StormTheCitadel extends CardImpl { + + private static final FilterPermanent filter + = new FilterPermanent("artifact or enchantment defending player controls"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.ENCHANTMENT) + )); + filter.add(DefendingPlayerControlsPredicate.instance); + } + + public StormTheCitadel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); + + // Until end of turn, creatures you control get +2/+2 and gain "Whenever this creature deals combat damage to a creature or planeswalker, destroy target artifact or enchantment defending player controls." + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility( + new DestroyTargetEffect(), false + ).setOrPlaneswalker(true); + ability.addTarget(new TargetPermanent(filter)); + + this.getSpellAbility().addEffect(new BoostControlledEffect( + 2, 2, Duration.EndOfTurn + ).setText("Until end of turn, creatures you control get +2/+2")); + + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + ability, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain \"Whenever this creature deals combat damage to a player or planeswalker, " + + "destroy target artifact or enchantment defending player controls.\"" + )); + } + + private StormTheCitadel(final StormTheCitadel card) { + super(card); + } + + @Override + public StormTheCitadel copy() { + return new StormTheCitadel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StorrevDevkarinLich.java b/Mage.Sets/src/mage/cards/s/StorrevDevkarinLich.java index fe0ee735e7..1190dcee43 100644 --- a/Mage.Sets/src/mage/cards/s/StorrevDevkarinLich.java +++ b/Mage.Sets/src/mage/cards/s/StorrevDevkarinLich.java @@ -106,7 +106,7 @@ class StorrevDevkarinLichWatcher extends Watcher { if (event.getType() == GameEvent.EventType.ZONE_CHANGE && ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { Card card = game.getCard(event.getTargetId()); - if (card != null && card.isCreature()) { + if (card != null && (card.isCreature() || card.isPlaneswalker())) { cards.add(new MageObjectReference(card, game)); } } diff --git a/Mage.Sets/src/mage/cards/s/StrataScythe.java b/Mage.Sets/src/mage/cards/s/StrataScythe.java index 21f8c0645e..9e362b19ff 100644 --- a/Mage.Sets/src/mage/cards/s/StrataScythe.java +++ b/Mage.Sets/src/mage/cards/s/StrataScythe.java @@ -69,7 +69,7 @@ class StrataScytheImprintEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(new FilterLandCard()); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { UUID cardId = target.getTargets().get(0); Card card = player.getLibrary().remove(cardId, game); diff --git a/Mage.Sets/src/mage/cards/s/SunbladeAngel.java b/Mage.Sets/src/mage/cards/s/SunbladeAngel.java new file mode 100644 index 0000000000..4959a4d793 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunbladeAngel.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunbladeAngel extends CardImpl { + + public SunbladeAngel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + } + + private SunbladeAngel(final SunbladeAngel card) { + super(card); + } + + @Override + public SunbladeAngel copy() { + return new SunbladeAngel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Sunforger.java b/Mage.Sets/src/mage/cards/s/Sunforger.java index f6bc12430a..fe70654e64 100644 --- a/Mage.Sets/src/mage/cards/s/Sunforger.java +++ b/Mage.Sets/src/mage/cards/s/Sunforger.java @@ -104,7 +104,7 @@ class SunforgerEffect extends OneShotEffect { filter.add(new CardTypePredicate(CardType.INSTANT)); filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, 5)); filter.add(new CardCanBeCastPredicate(source.getControllerId())); - if (controller.searchLibrary(target, game, controller.getId())) { + if (controller.searchLibrary(target, source, game, controller.getId())) { UUID targetId = target.getFirstTarget(); Card card = game.getCard(targetId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/s/SupremeInquisitor.java b/Mage.Sets/src/mage/cards/s/SupremeInquisitor.java index 17fb5d6f11..e2a16b7fde 100644 --- a/Mage.Sets/src/mage/cards/s/SupremeInquisitor.java +++ b/Mage.Sets/src/mage/cards/s/SupremeInquisitor.java @@ -87,7 +87,7 @@ class SupremeInquisitorEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && targetPlayer != null) { TargetCardInLibrary target = new TargetCardInLibrary(0, 5, filter); - if (player.searchLibrary(target, game, targetPlayer.getId())) { + if (player.searchLibrary(target, source, game, targetPlayer.getId())) { List targetId = target.getTargets(); for (UUID targetCard : targetId) { Card card = targetPlayer.getLibrary().remove(targetCard, game); diff --git a/Mage.Sets/src/mage/cards/s/SurgicalExtraction.java b/Mage.Sets/src/mage/cards/s/SurgicalExtraction.java index e1f0e8ff73..b660eb088e 100644 --- a/Mage.Sets/src/mage/cards/s/SurgicalExtraction.java +++ b/Mage.Sets/src/mage/cards/s/SurgicalExtraction.java @@ -118,7 +118,7 @@ class SurgicalExtractionEffect extends OneShotEffect { // cards in Library filterNamedCard.setMessage("card named " + nameToSearch + " in the library of " + owner.getName()); TargetCardInLibrary targetCardInLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCard); - if (controller.searchLibrary(targetCardInLibrary, game, owner.getId())) { + if (controller.searchLibrary(targetCardInLibrary, source, game, owner.getId())) { List targets = targetCardInLibrary.getTargets(); for (UUID targetId : targets) { Card targetCard = owner.getLibrary().getCard(targetId, game); diff --git a/Mage.Sets/src/mage/cards/s/SylvanHierophant.java b/Mage.Sets/src/mage/cards/s/SylvanHierophant.java new file mode 100644 index 0000000000..80555a1adc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SylvanHierophant.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.AnotherCardPredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + + +/** + * + * @author jmharmon + */ + +public final class SylvanHierophant extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("another target creature card from your graveyard"); + + static { + filter.add(new AnotherCardPredicate()); + } + + public SylvanHierophant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When Sylvan Hierophant dies, exile Sylvan Hierophant, then return another target creature card from your graveyard to your hand. + Effect effect = new ReturnFromGraveyardToHandTargetEffect(); + Ability ability = new DiesTriggeredAbility(new ExileSourceEffect(), false); + ability.addEffect(effect); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + public SylvanHierophant(final SylvanHierophant card) { + super(card); + } + + @Override + public SylvanHierophant copy() { + return new SylvanHierophant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TaigamOjutaiMaster.java b/Mage.Sets/src/mage/cards/t/TaigamOjutaiMaster.java index 2ea1bd128e..24ac35adcd 100644 --- a/Mage.Sets/src/mage/cards/t/TaigamOjutaiMaster.java +++ b/Mage.Sets/src/mage/cards/t/TaigamOjutaiMaster.java @@ -1,4 +1,3 @@ - package mage.cards.t; import mage.MageInt; @@ -16,7 +15,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterSpell; -import mage.filter.FilterStackObject; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.SubtypePredicate; @@ -30,7 +29,6 @@ import mage.watchers.common.AttackedThisTurnWatcher; import java.util.UUID; /** - * * @author spjspj */ public final class TaigamOjutaiMaster extends CardImpl { @@ -57,8 +55,8 @@ public final class TaigamOjutaiMaster extends CardImpl { this.toughness = new MageInt(4); // Instant, sorcery, and Dragon spells you control can't be countered. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeCounteredControlledEffect(filter, new FilterStackObject(), Duration.WhileOnBattlefield))); - + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeCounteredControlledEffect(filter, StaticFilters.FILTER_SPELL_OR_ABILITY, Duration.WhileOnBattlefield))); + // Whenever you cast an instant or sorcery spell from your hand, if Taigam, Ojutai Master attacked this turn, that spell gains rebound. Ability ability = new ConditionalInterveningIfTriggeredAbility(new TaigamOjutaiMasterTriggeredAbility(), AttackedThisTurnSourceCondition.instance, diff --git a/Mage.Sets/src/mage/cards/t/TamiyoCollectorOfTales.java b/Mage.Sets/src/mage/cards/t/TamiyoCollectorOfTales.java new file mode 100644 index 0000000000..b2352caa2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TamiyoCollectorOfTales.java @@ -0,0 +1,157 @@ +package mage.cards.t; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.*; +import mage.cards.repository.CardRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.PermanentCard; +import mage.game.stack.Spell; +import mage.game.stack.StackAbility; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +import static mage.constants.Outcome.Benefit; + +/** + * @author TheElk801 + */ +public final class TamiyoCollectorOfTales extends CardImpl { + + public TamiyoCollectorOfTales(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.TAMIYO); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); + + // Spells and abilities your opponents control can't cause you to discard cards or sacrifice permanents. + this.addAbility(new SimpleStaticAbility(new TamiyoCollectorOfTalesRuleEffect())); + + // +1: Choose a nonland card name, then reveal the top four cards of your library. Put all cards with the chosen name from among them into your hand and the rest into your graveyard. + this.addAbility(new LoyaltyAbility(new TamiyoCollectorOfTalesEffect(), 1)); + + // -3: Return target card from your graveyard to your hand. + Ability ability = new LoyaltyAbility(new ReturnToHandTargetEffect(), -3); + ability.addTarget(new TargetCardInYourGraveyard()); + this.addAbility(ability); + } + + private TamiyoCollectorOfTales(final TamiyoCollectorOfTales card) { + super(card); + } + + @Override + public TamiyoCollectorOfTales copy() { + return new TamiyoCollectorOfTales(this); + } +} + +class TamiyoCollectorOfTalesRuleEffect extends ContinuousRuleModifyingEffectImpl { + + TamiyoCollectorOfTalesRuleEffect() { + super(Duration.WhileOnBattlefield, Benefit); + staticText = "Spells and abilities your opponents control can't " + + "cause you to discard cards or sacrifice permanents"; + } + + private TamiyoCollectorOfTalesRuleEffect(final TamiyoCollectorOfTalesRuleEffect effect) { + super(effect); + } + + @Override + public TamiyoCollectorOfTalesRuleEffect copy() { + return new TamiyoCollectorOfTalesRuleEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SACRIFICE_PERMANENT + || event.getType() == GameEvent.EventType.DISCARD_CARD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getPlayerId().equals(source.getControllerId())) { + MageObject object = game.getObject(event.getSourceId()); + if (object instanceof PermanentCard) { + if (game.getOpponents(source.getControllerId()).contains(((PermanentCard) object).getControllerId())) { + return true; + } + } + if (object instanceof Spell) { + if (game.getOpponents(source.getControllerId()).contains(((Spell) object).getControllerId())) { + return true; + } + } + if (object instanceof Card) { + if (game.getOpponents(source.getControllerId()).contains(((Card) object).getOwnerId())) { + return true; + } + } + if (object instanceof StackAbility) { + if (game.getOpponents(source.getControllerId()).contains(((StackAbility) object).getControllerId())) { + return true; + } + } + } + return false; + } +} + +class TamiyoCollectorOfTalesEffect extends OneShotEffect { + + TamiyoCollectorOfTalesEffect() { + super(Outcome.Benefit); + staticText = "Choose a nonland card name, then reveal the top four cards of your library. " + + "Put all cards with the chosen name from among them into your hand and the rest into your graveyard."; + } + + private TamiyoCollectorOfTalesEffect(final TamiyoCollectorOfTalesEffect effect) { + super(effect); + } + + @Override + public TamiyoCollectorOfTalesEffect copy() { + return new TamiyoCollectorOfTalesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Choice choice = new ChoiceImpl(); + choice.setChoices(CardRepository.instance.getNonLandNames()); + choice.setMessage("Choose a nonland card name"); + if (!player.choose(outcome, choice, game)) { + return false; + } + game.informPlayers(source.getSourceObject(game).getLogName() + ", chosen name: [" + choice.getChoice() + ']'); + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 4)); + Cards cards2 = new CardsImpl(); + player.revealCards(source, cards, game); + for (Card card : cards.getCards(game)) { + if (card.getName().equals(choice.getChoice())) { + cards2.add(card); + } + } + cards.removeAll(cards2); + player.moveCards(cards, Zone.GRAVEYARD, source, game); + player.moveCards(cards2, Zone.HAND, source, game); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TamiyosEpiphany.java b/Mage.Sets/src/mage/cards/t/TamiyosEpiphany.java new file mode 100644 index 0000000000..bd626fa4f0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TamiyosEpiphany.java @@ -0,0 +1,32 @@ +package mage.cards.t; + +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TamiyosEpiphany extends CardImpl { + + public TamiyosEpiphany(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); + + // Scry 4, then draw two cards. + this.getSpellAbility().addEffect(new ScryEffect(4).setText("scry 4,")); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).setText("then draw two cards")); + } + + private TamiyosEpiphany(final TamiyosEpiphany card) { + super(card); + } + + @Override + public TamiyosEpiphany copy() { + return new TamiyosEpiphany(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Taunt.java b/Mage.Sets/src/mage/cards/t/Taunt.java index cff6d3e77c..60de9ffbf7 100644 --- a/Mage.Sets/src/mage/cards/t/Taunt.java +++ b/Mage.Sets/src/mage/cards/t/Taunt.java @@ -1,7 +1,5 @@ - package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.cards.CardImpl; @@ -13,14 +11,15 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author emerald000 */ public final class Taunt extends CardImpl { public Taunt(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{U}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}"); // During target player's next turn, creatures that player controls attack you if able. this.getSpellAbility().addEffect(new TauntEffect()); @@ -60,9 +59,7 @@ class TauntEffect extends RequirementEffect { @Override public boolean isInactive(Ability source, Game game) { - return startingTurn != game.getTurnNum() && - (game.getPhase().getType() == TurnPhase.END && - game.isActivePlayer(this.getTargetPointer().getFirst(game, source))); + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TawnossCoffin.java b/Mage.Sets/src/mage/cards/t/TawnossCoffin.java index af280935ff..45fef01174 100644 --- a/Mage.Sets/src/mage/cards/t/TawnossCoffin.java +++ b/Mage.Sets/src/mage/cards/t/TawnossCoffin.java @@ -1,8 +1,5 @@ package mage.cards.t; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -32,8 +29,11 @@ import mage.target.Target; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author MarcoMarin */ public final class TawnossCoffin extends CardImpl { @@ -194,7 +194,7 @@ class TawnossCoffinReturnEffect extends OneShotEffect { if (notedCounters != null) { for (Counter c : notedCounters.values()) { //would be nice if could just use that copy function to set the whole field if (c != null) { - newPermanent.getCounters(game).addCounter(c); + newPermanent.getCounters(game).addCounter(c); // it's restore counters, not add (e.g. without add events) } } } diff --git a/Mage.Sets/src/mage/cards/t/TeferiTimeRaveler.java b/Mage.Sets/src/mage/cards/t/TeferiTimeRaveler.java index f35b55d7b4..803f9475ec 100644 --- a/Mage.Sets/src/mage/cards/t/TeferiTimeRaveler.java +++ b/Mage.Sets/src/mage/cards/t/TeferiTimeRaveler.java @@ -58,7 +58,7 @@ public final class TeferiTimeRaveler extends CardImpl { // -3: Return up to one target artifact, creature, or enchantment to its owner's hand. Draw a card. Ability ability = new LoyaltyAbility(new ReturnToHandTargetEffect(), -3); ability.addEffect(new DrawCardSourceControllerEffect(1).setText("Draw a card")); - ability.addTarget(new TargetPermanent(filter2)); + ability.addTarget(new TargetPermanent(0, 1, filter2, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java b/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java new file mode 100644 index 0000000000..a6395dd0dd --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java @@ -0,0 +1,123 @@ +package mage.cards.t; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +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.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TeferisTimeTwist extends CardImpl { + + public TeferisTimeTwist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Exile target permanent you control. Return that card to the battlefield under its owner's control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it. + this.getSpellAbility().addEffect(new TeferisTimeTwistEffect()); + this.getSpellAbility().addTarget(new TargetControlledPermanent()); + } + + private TeferisTimeTwist(final TeferisTimeTwist card) { + super(card); + } + + @Override + public TeferisTimeTwist copy() { + return new TeferisTimeTwist(this); + } +} + +class TeferisTimeTwistEffect extends OneShotEffect { + + TeferisTimeTwistEffect() { + super(Outcome.Benefit); + staticText = "Exile target permanent you control. Return that card to the battlefield " + + "under its owner's control at the beginning of the next end step. " + + "If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it."; + } + + private TeferisTimeTwistEffect(final TeferisTimeTwistEffect effect) { + super(effect); + } + + @Override + public TeferisTimeTwistEffect copy() { + return new TeferisTimeTwistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + Player player = game.getPlayer(source.getControllerId()); + if (permanent == null || player == null) { + return false; + } + Effect effect = new TeferisTimeTwistReturnEffect(new MageObjectReference( + permanent.getId(), permanent.getZoneChangeCounter(game) + 1, game + )); + if (!player.moveCards(permanent, Zone.EXILED, source, game)) { + return false; + } + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + return true; + } +} + +class TeferisTimeTwistReturnEffect extends OneShotEffect { + + private final MageObjectReference mor; + + TeferisTimeTwistReturnEffect(MageObjectReference mor) { + super(Outcome.Benefit); + staticText = "return the exiled card to the battlefield"; + this.mor = mor; + } + + private TeferisTimeTwistReturnEffect(final TeferisTimeTwistReturnEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public TeferisTimeTwistReturnEffect copy() { + return new TeferisTimeTwistReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = mor.getCard(game); + if (card == null) { + return false; + } + Player player = game.getPlayer(card.getOwnerId()); + if (player == null) { + return false; + } + if (!player.moveCards(card, Zone.BATTLEFIELD, source, game)) { + return true; + } + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null && permanent.isCreature()) { + // TODO: This is technically wrong as it should enter with the counters, + // however there's currently no way to know that for sure + // this is similar to the blood moon issue + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TemporalAperture.java b/Mage.Sets/src/mage/cards/t/TemporalAperture.java index 7cedc4b5f7..1bd65e5d27 100644 --- a/Mage.Sets/src/mage/cards/t/TemporalAperture.java +++ b/Mage.Sets/src/mage/cards/t/TemporalAperture.java @@ -52,7 +52,11 @@ class TemporalApertureEffect extends OneShotEffect { public TemporalApertureEffect() { super(Outcome.Neutral); - staticText = "Shuffle your library, then reveal the top card. Until end of turn, for as long as that card remains on top of your library, play with the top card of your library revealed and you may play that card without paying its mana cost"; + staticText = "Shuffle your library, then reveal the top card. " + + "Until end of turn, for as long as that card remains on " + + "top of your library, play with the top card of your " + + "library revealed and you may play that card without " + + "paying its mana cost"; } public TemporalApertureEffect(final TemporalApertureEffect effect) { @@ -89,7 +93,8 @@ class TemporalApertureTopCardCastEffect extends AsThoughEffectImpl { public TemporalApertureTopCardCastEffect(Card card) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); this.card = card; - staticText = "Until end of turn, for as long as that card is on top of your library, you may cast it without paying its mana costs"; + staticText = "Until end of turn, for as long as that card is on top " + + "of your library, you may cast it without paying its mana costs"; } public TemporalApertureTopCardCastEffect(final TemporalApertureTopCardCastEffect effect) { @@ -118,7 +123,8 @@ class TemporalApertureTopCardCastEffect extends AsThoughEffectImpl { if (controller.getLibrary().getFromTop(game).equals(card)) { if (objectCard == card && objectCard.getSpellAbility() != null - && objectCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game)) { + && objectCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game) + || objectCard.isLand()) { controller.setCastSourceIdWithAlternateMana(objectId, null, null); return true; } diff --git a/Mage.Sets/src/mage/cards/t/TemptWithDiscovery.java b/Mage.Sets/src/mage/cards/t/TemptWithDiscovery.java index ae5e1b3c85..f1fb697b30 100644 --- a/Mage.Sets/src/mage/cards/t/TemptWithDiscovery.java +++ b/Mage.Sets/src/mage/cards/t/TemptWithDiscovery.java @@ -66,7 +66,7 @@ class TemptWithDiscoveryEffect extends OneShotEffect { Set playersShuffle = new LinkedHashSet<>(); playersShuffle.add(controller.getId()); TargetCardInLibrary target = new TargetCardInLibrary(new FilterLandCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { for (UUID cardId : target.getTargets()) { Card card = game.getCard(cardId); if (card != null) { @@ -82,7 +82,7 @@ class TemptWithDiscoveryEffect extends OneShotEffect { target.clearChosen(); opponentsUsedSearch++; playersShuffle.add(playerId); - if (opponent.searchLibrary(target, game)) { + if (opponent.searchLibrary(target, source, game)) { for (UUID cardId : target.getTargets()) { Card card = game.getCard(cardId); if (card != null) { @@ -95,7 +95,7 @@ class TemptWithDiscoveryEffect extends OneShotEffect { } if (opponentsUsedSearch > 0) { target = new TargetCardInLibrary(0, opponentsUsedSearch, new FilterLandCard()); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { for (UUID cardId : target.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/t/TenthDistrictLegionnaire.java b/Mage.Sets/src/mage/cards/t/TenthDistrictLegionnaire.java index 0391f1e5fa..3f1dd36652 100644 --- a/Mage.Sets/src/mage/cards/t/TenthDistrictLegionnaire.java +++ b/Mage.Sets/src/mage/cards/t/TenthDistrictLegionnaire.java @@ -1,5 +1,6 @@ package mage.cards.t; +import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -12,8 +13,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import java.util.UUID; - /** * @author TheElk801 */ @@ -34,7 +33,7 @@ public final class TenthDistrictLegionnaire extends CardImpl { Ability ability = new HeroicAbility(new AddCountersSourceEffect( CounterType.P1P1.createInstance() ), false, false); - ability.addEffect(new ScryEffect(1)); + ability.addEffect(new ScryEffect(1).setText(", then scry 1")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TerentatekCub.java b/Mage.Sets/src/mage/cards/t/TerentatekCub.java index 77f191bad0..0634db6c4c 100644 --- a/Mage.Sets/src/mage/cards/t/TerentatekCub.java +++ b/Mage.Sets/src/mage/cards/t/TerentatekCub.java @@ -1,11 +1,12 @@ - package mage.cards.t; -import java.util.UUID; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.OpponentControlsPermanentCondition; import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalRequirementEffect; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.combat.AttacksIfAbleSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.cards.CardImpl; @@ -18,8 +19,9 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SubtypePredicate; +import java.util.UUID; + /** - * * @author Styxo */ public final class TerentatekCub extends CardImpl { @@ -36,17 +38,15 @@ public final class TerentatekCub extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); - // As long as an opponent controls a Jedi or Sith, {this} gets +1/+1 and attacks each turn if able - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( + // As long as an opponent controls a Jedi or Sith, {this} gets +1/+1 and attacks each turn if able + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new BoostSourceEffect(1, 1, Duration.Custom), new OpponentControlsPermanentCondition(filter), - "As long as an opponent controls a Jedi or Sith, {this} gets +1/+1 ") - )); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new AttacksIfAbleSourceEffect(Duration.Custom), - new OpponentControlsPermanentCondition(filter), - "and attacks each turn if able.") - )); + "As long as an opponent controls a Jedi or Sith, {this} gets +1/+1")); + Effect effect = new ConditionalRequirementEffect(new AttacksIfAbleSourceEffect(Duration.Custom), new OpponentControlsPermanentCondition(filter)); + effect.setText("and attacks each turn if able"); + ability.addEffect(effect); + this.addAbility(ability); } public TerentatekCub(final TerentatekCub card) { diff --git a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java index 455c29a1db..5171330f06 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretMasterOfTheBridge.java @@ -125,7 +125,7 @@ class TezzeretMasterOfTheBridgeEffect2 extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getSourceId()); + Player player = game.getPlayer(source.getControllerId()); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TezzeretTheSeeker.java b/Mage.Sets/src/mage/cards/t/TezzeretTheSeeker.java index c9f2ff0ed6..bf93fc6fb6 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretTheSeeker.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretTheSeeker.java @@ -91,7 +91,7 @@ class TezzeretTheSeekerEffect2 extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, cmc + 1)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/t/TezzeretsGambit.java b/Mage.Sets/src/mage/cards/t/TezzeretsGambit.java index d616e448d7..c481b3bc77 100644 --- a/Mage.Sets/src/mage/cards/t/TezzeretsGambit.java +++ b/Mage.Sets/src/mage/cards/t/TezzeretsGambit.java @@ -1,24 +1,24 @@ - package mage.cards.t; -import java.util.UUID; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import java.util.UUID; + /** - * * @author North */ public final class TezzeretsGambit extends CardImpl { public TezzeretsGambit(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U/P}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U/P}"); + // Draw two cards, then proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); - this.getSpellAbility().addEffect(new ProliferateEffect()); + this.getSpellAbility().addEffect(new ProliferateEffect().concatBy(", then")); } public TezzeretsGambit(final TezzeretsGambit card) { diff --git a/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java b/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java index e5777b82a6..e7e02a1e2c 100644 --- a/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java +++ b/Mage.Sets/src/mage/cards/t/ThadaAdelAcquisitor.java @@ -72,7 +72,7 @@ class ThadaAdelAcquisitorEffect extends OneShotEffect { return false; } TargetCardInLibrary target = new TargetCardInLibrary(new FilterArtifactCard()); - if (controller.searchLibrary(target, game, damagedPlayer.getId())) { + if (controller.searchLibrary(target, source, game, damagedPlayer.getId())) { if (!target.getTargets().isEmpty()) { Card card = damagedPlayer.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/t/TheElderspell.java b/Mage.Sets/src/mage/cards/t/TheElderspell.java new file mode 100644 index 0000000000..0251cb9377 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheElderspell.java @@ -0,0 +1,87 @@ +package mage.cards.t; + +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.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheElderspell extends CardImpl { + + public TheElderspell(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{B}"); + + // Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. + this.getSpellAbility().addEffect(new TheElderspellEffect()); + this.getSpellAbility().addTarget(new TargetPermanent( + 0, Integer.MAX_VALUE, StaticFilters.FILTER_PERMANENT_PLANESWALKERS, false + )); + } + + private TheElderspell(final TheElderspell card) { + super(card); + } + + @Override + public TheElderspell copy() { + return new TheElderspell(this); + } +} + +class TheElderspellEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPlaneswalkerPermanent(); + + TheElderspellEffect() { + super(Outcome.Benefit); + staticText = "Destroy any number of target planeswalkers. Choose a planeswalker you control. " + + "Put two loyalty counters on it for each planeswalker destroyed this way."; + } + + private TheElderspellEffect(final TheElderspellEffect effect) { + super(effect); + } + + @Override + public TheElderspellEffect copy() { + return new TheElderspellEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int count = 0; + for (UUID permId : source.getTargets().get(0).getTargets()) { + Permanent permanent = game.getPermanent(permId); + if (permanent != null && permanent.destroy(source.getSourceId(), game, false)) { + count++; + } + } + TargetPermanent targetPermanent = new TargetPermanent(filter); + if (!player.choose(outcome, targetPermanent, source.getSourceId(), game)) { + return false; + } + Permanent permanent = game.getPermanent(targetPermanent.getFirstTarget()); + if (permanent == null) { + return false; + } + return permanent.addCounters(CounterType.LOYALTY.createInstance(2 * count), source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheMimeoplasm.java b/Mage.Sets/src/mage/cards/t/TheMimeoplasm.java index e30d3e8cda..888e1b8beb 100644 --- a/Mage.Sets/src/mage/cards/t/TheMimeoplasm.java +++ b/Mage.Sets/src/mage/cards/t/TheMimeoplasm.java @@ -70,12 +70,14 @@ class TheMimeoplasmEffect extends OneShotEffect { if (new CardsInAllGraveyardsCount(new FilterCreatureCard()).calculate(game, source, this) >= 2) { if (controller.chooseUse(Outcome.Benefit, "Do you want to exile two creature cards from graveyards?", source, game)) { TargetCardInGraveyard targetCopy = new TargetCardInGraveyard(new FilterCreatureCard("creature card to become a copy of")); + targetCopy.setNotTarget(true); if (controller.choose(Outcome.Copy, targetCopy, source.getSourceId(), game)) { Card cardToCopy = game.getCard(targetCopy.getFirstTarget()); if (cardToCopy != null) { FilterCreatureCard filter = new FilterCreatureCard("creature card to determine amount of additional +1/+1 counters"); filter.add(Predicates.not(new CardIdPredicate(cardToCopy.getId()))); TargetCardInGraveyard targetCounters = new TargetCardInGraveyard(filter); + targetCounters.setNotTarget(true); if (controller.choose(Outcome.Copy, targetCounters, source.getSourceId(), game)) { Card cardForCounters = game.getCard(targetCounters.getFirstTarget()); if (cardForCounters != null) { diff --git a/Mage.Sets/src/mage/cards/t/TheWanderer.java b/Mage.Sets/src/mage/cards/t/TheWanderer.java index 4bdfe787fc..f1820f5968 100644 --- a/Mage.Sets/src/mage/cards/t/TheWanderer.java +++ b/Mage.Sets/src/mage/cards/t/TheWanderer.java @@ -20,12 +20,15 @@ import mage.filter.predicate.permanent.AnotherPredicate; import mage.target.TargetPermanent; import java.util.UUID; +import mage.abilities.effects.common.PreventDamageToControllerEffect; /** * @author TheElk801 */ public final class TheWanderer extends CardImpl { + private static final String rule = "Prevent all noncombat damage that " + + "would be dealt to you and other permanents you control."; private static final FilterPermanent filter = new FilterControlledPermanent("other permanents you control"); private static final FilterPermanent filter2 @@ -43,11 +46,15 @@ public final class TheWanderer extends CardImpl { this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); // Prevent all noncombat damage that would be dealt to you and other permanents you control. + this.addAbility(new SimpleStaticAbility( + new PreventDamageToControllerEffect( + Duration.WhileOnBattlefield, true, false, + Integer.MAX_VALUE + ).setText(rule))); this.addAbility(new SimpleStaticAbility( new PreventAllNonCombatDamageToAllEffect( Duration.WhileOnBattlefield, filter, true - ) - )); + ).setText(""))); // -2: Exile target creature with power 4 or greater. Ability ability = new LoyaltyAbility(new ExileTargetEffect(), -2); diff --git a/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java b/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java index 5b0da23fd5..04f7a06f21 100644 --- a/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java +++ b/Mage.Sets/src/mage/cards/t/ThoughtHemorrhage.java @@ -112,7 +112,7 @@ class ThoughtHemorrhageEffect extends OneShotEffect { // search cards in Library // If the player has no nonland cards in their hand, you can still search that player's library and have him or her shuffle it. TargetCardInLibrary targetCardsLibrary = new TargetCardInLibrary(0, Integer.MAX_VALUE, filterNamedCards); - controller.searchLibrary(targetCardsLibrary, game, targetPlayer.getId()); + controller.searchLibrary(targetCardsLibrary, source, game, targetPlayer.getId()); for (UUID cardId : targetCardsLibrary.getTargets()) { Card card = game.getCard(cardId); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/t/ThroneOfGeth.java b/Mage.Sets/src/mage/cards/t/ThroneOfGeth.java index 329851cf1b..b71086bd71 100644 --- a/Mage.Sets/src/mage/cards/t/ThroneOfGeth.java +++ b/Mage.Sets/src/mage/cards/t/ThroneOfGeth.java @@ -1,8 +1,5 @@ - - package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; @@ -16,8 +13,9 @@ import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author Loki */ public final class ThroneOfGeth extends CardImpl { @@ -30,9 +28,9 @@ public final class ThroneOfGeth extends CardImpl { public ThroneOfGeth(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{2}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - // {T}, Sacrifice an artifact: Proliferate. + // {T}, Sacrifice an artifact: Proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ProliferateEffect(), new TapSourceCost()); ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/t/Thrummingbird.java b/Mage.Sets/src/mage/cards/t/Thrummingbird.java index 9156fc33e5..15be366d09 100644 --- a/Mage.Sets/src/mage/cards/t/Thrummingbird.java +++ b/Mage.Sets/src/mage/cards/t/Thrummingbird.java @@ -1,8 +1,5 @@ - - package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.common.counter.ProliferateEffect; @@ -12,20 +9,25 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import java.util.UUID; + /** * @author Loki, nantuko, North */ public final class Thrummingbird extends CardImpl { public Thrummingbird(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); this.subtype.add(SubType.BIRD); this.subtype.add(SubType.HORROR); this.power = new MageInt(1); this.toughness = new MageInt(1); + // Flying this.addAbility(FlyingAbility.getInstance()); + + // Whenever Thrummingbird deals combat damage to a player, proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new ProliferateEffect(), false)); } diff --git a/Mage.Sets/src/mage/cards/t/ThunderDrake.java b/Mage.Sets/src/mage/cards/t/ThunderDrake.java new file mode 100644 index 0000000000..6ee8ae338e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderDrake.java @@ -0,0 +1,84 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +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.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.common.CastSpellLastTurnWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThunderDrake extends CardImpl { + + public ThunderDrake(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.DRAKE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast you cast your second spell each turn, put a +1/+1 counter on Thunder Drake. + this.addAbility(new ThunderDrakeTriggeredAbility(), new CastSpellLastTurnWatcher()); + } + + private ThunderDrake(final ThunderDrake card) { + super(card); + } + + @Override + public ThunderDrake copy() { + return new ThunderDrake(this); + } +} + +class ThunderDrakeTriggeredAbility extends TriggeredAbilityImpl { + + ThunderDrakeTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + } + + private ThunderDrakeTriggeredAbility(final ThunderDrakeTriggeredAbility ability) { + super(ability); + } + + @Override + public ThunderDrakeTriggeredAbility copy() { + return new ThunderDrakeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(controllerId)) { + CastSpellLastTurnWatcher watcher = game.getState().getWatcher(CastSpellLastTurnWatcher.class); + if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 2) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you cast your second spell each turn, put a +1/+1 counter on {this}"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderingCeratok.java b/Mage.Sets/src/mage/cards/t/ThunderingCeratok.java new file mode 100644 index 0000000000..6d5ec6a0b6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderingCeratok.java @@ -0,0 +1,46 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +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.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThunderingCeratok extends CardImpl { + + public ThunderingCeratok(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.RHINO); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Thundering Ceratok enters the battlefield, other creatures you control gain trample until end of turn. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES, true + ))); + } + + private ThunderingCeratok(final ThunderingCeratok card) { + super(card); + } + + @Override + public ThunderingCeratok copy() { + return new ThunderingCeratok(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TibaltsRager.java b/Mage.Sets/src/mage/cards/t/TibaltsRager.java index 16d0c99782..26d3282571 100644 --- a/Mage.Sets/src/mage/cards/t/TibaltsRager.java +++ b/Mage.Sets/src/mage/cards/t/TibaltsRager.java @@ -31,6 +31,7 @@ public final class TibaltsRager extends CardImpl { // When Tibalt's Rager dies, it deals 1 damage to any target. Ability ability = new DiesTriggeredAbility(new DamageTargetEffect(1, "it")); ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); // {1}{R}: Tibalt's Rager gets +2/+0 until end of turn. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/t/TidehollowSculler.java b/Mage.Sets/src/mage/cards/t/TidehollowSculler.java index b1e2bacc96..16341e8693 100644 --- a/Mage.Sets/src/mage/cards/t/TidehollowSculler.java +++ b/Mage.Sets/src/mage/cards/t/TidehollowSculler.java @@ -17,7 +17,6 @@ import mage.constants.Zone; import mage.filter.common.FilterNonlandCard; import mage.game.ExileZone; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.players.Player; import mage.target.TargetCard; @@ -79,11 +78,9 @@ class TidehollowScullerExileEffect extends OneShotEffect { // 6/7/2013 If Tidehollow Sculler leaves the battlefield before its first ability has resolved, // its second ability will trigger. This ability will do nothing when it resolves. // Then its first ability will resolve and exile the chosen card forever. - Permanent sourcePermanent = (Permanent) source.getSourcePermanentIfItStillExists(game); if (controller != null - && opponent != null - && sourcePermanent != null) { - opponent.revealCards(sourcePermanent.getName(), opponent.getHand(), game); + && opponent != null) { + opponent.revealCards("Tidehollow Sculler", opponent.getHand(), game); TargetCard target = new TargetCard(Zone.HAND, new FilterNonlandCard("nonland card to exile")); if (controller.choose(Outcome.Exile, opponent.getHand(), target, game)) { Card card = opponent.getHand().get(target.getFirstTarget(), game); @@ -96,7 +93,7 @@ class TidehollowScullerExileEffect extends OneShotEffect { CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), - sourcePermanent.getIdName()); + "Tidehollow Sculler"); } } return true; @@ -126,8 +123,8 @@ class TidehollowScullerLeaveEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { - int zoneChangeCounter = (sourceObject instanceof PermanentToken) - ? source.getSourceObjectZoneChangeCounter() + int zoneChangeCounter = (sourceObject instanceof PermanentToken) + ? source.getSourceObjectZoneChangeCounter() : source.getSourceObjectZoneChangeCounter() - 1; ExileZone exZone = game.getExile().getExileZone( CardUtil.getExileZoneId(game, source.getSourceId(), zoneChangeCounter)); diff --git a/Mage.Sets/src/mage/cards/t/TithebearerGiant.java b/Mage.Sets/src/mage/cards/t/TithebearerGiant.java new file mode 100644 index 0000000000..b3ba27a062 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TithebearerGiant.java @@ -0,0 +1,44 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TithebearerGiant extends CardImpl { + + public TithebearerGiant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}"); + + this.subtype.add(SubType.GIANT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // When Tithebearer Giant enters the battlefield, you draw a card and you lose 1 life. + Ability ability = new EntersBattlefieldTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private TithebearerGiant(final TithebearerGiant card) { + super(card); + } + + @Override + public TithebearerGiant copy() { + return new TithebearerGiant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TollOfTheInvasion.java b/Mage.Sets/src/mage/cards/t/TollOfTheInvasion.java new file mode 100644 index 0000000000..88a1bf0e13 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TollOfTheInvasion.java @@ -0,0 +1,40 @@ +package mage.cards.t; + +import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.abilities.effects.keyword.AmassEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TollOfTheInvasion extends CardImpl { + + public TollOfTheInvasion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Target opponent reveals their hand. You choose a nonland card from it. That player discards that card. + this.getSpellAbility().addEffect(new DiscardCardYouChooseTargetEffect( + StaticFilters.FILTER_CARD_NON_LAND, TargetController.OPPONENT + )); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Amass 1. + this.getSpellAbility().addEffect(new AmassEffect(1)); + } + + private TollOfTheInvasion(final TollOfTheInvasion card) { + super(card); + } + + @Override + public TollOfTheInvasion copy() { + return new TollOfTheInvasion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TolsimirFriendToWolves.java b/Mage.Sets/src/mage/cards/t/TolsimirFriendToWolves.java new file mode 100644 index 0000000000..45f202a641 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TolsimirFriendToWolves.java @@ -0,0 +1,132 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.VojaFriendToElvesToken; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +import static mage.constants.Outcome.Benefit; + +/** + * @author TheElk801 + */ +public final class TolsimirFriendToWolves extends CardImpl { + + public TolsimirFriendToWolves(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Tolsimir, Friend to Wolves enters the battlefield, create Voja, Friend to Elves, a legendary 3/3 green and white Wolf creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new VojaFriendToElvesToken()))); + + // Whenever a Wolf enters the battlefield under your control, you gain 3 life and that creature fights up to one target creature an opponent controls. + this.addAbility(new TolsimirFriendToWolvesTriggeredAbility()); + } + + private TolsimirFriendToWolves(final TolsimirFriendToWolves card) { + super(card); + } + + @Override + public TolsimirFriendToWolves copy() { + return new TolsimirFriendToWolves(this); + } +} + +class TolsimirFriendToWolvesTriggeredAbility extends TriggeredAbilityImpl { + + TolsimirFriendToWolvesTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + this.addTarget(new TargetOpponentsCreaturePermanent(0, 1)); + } + + private TolsimirFriendToWolvesTriggeredAbility(final TolsimirFriendToWolvesTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null + || !permanent.isControlledBy(getControllerId()) + || !permanent.hasSubtype(SubType.WOLF, game)) { + return false; + } + this.getEffects().clear(); + this.addEffect(new TolsimirFriendToWolvesEffect(new MageObjectReference(permanent, game))); + return true; + } + + @Override + public TolsimirFriendToWolvesTriggeredAbility copy() { + return new TolsimirFriendToWolvesTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever a Wolf enters the battlefield under your control, " + + "you gain 3 life and that creature fights up to one target creature an opponent controls."; + } + +} + +class TolsimirFriendToWolvesEffect extends OneShotEffect { + + private final MageObjectReference wolfMor; + + TolsimirFriendToWolvesEffect(MageObjectReference wolfMor) { + super(Benefit); + this.wolfMor = wolfMor; + } + + private TolsimirFriendToWolvesEffect(final TolsimirFriendToWolvesEffect effect) { + super(effect); + this.wolfMor = effect.wolfMor; + } + + @Override + public TolsimirFriendToWolvesEffect copy() { + return new TolsimirFriendToWolvesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + new GainLifeEffect(3).apply(game, source); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return true; + } + Permanent wolf = wolfMor.getPermanent(game); + if (wolf == null) { + return false; + } + return wolf.fight(permanent, source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TomikDistinguishedAdvokist.java b/Mage.Sets/src/mage/cards/t/TomikDistinguishedAdvokist.java new file mode 100644 index 0000000000..905eaad9eb --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TomikDistinguishedAdvokist.java @@ -0,0 +1,161 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.CantBeTargetedAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterObject; +import mage.filter.FilterStackObject; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TomikDistinguishedAdvokist extends CardImpl { + + public TomikDistinguishedAdvokist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lands on the battlefield and land cards in graveyards can't be the targets of spells or abilities your opponents control. + FilterObject filter = new FilterStackObject(); + filter.add(new TargetedByOpponentsPredicate(this.getId())); + Ability ability = new SimpleStaticAbility(new CantBeTargetedAllEffect( + StaticFilters.FILTER_LANDS, filter, Duration.WhileOnBattlefield + ).setText("lands on the battlefield")); + ability.addEffect(new TomikDistinguishedAdvokistTargetEffect()); + this.addAbility(ability); + + // Your opponents can't play land cards from graveyards. + this.addAbility(new SimpleStaticAbility(new TomikDistinguishedAdvokistRestrictionEffect())); + } + + private TomikDistinguishedAdvokist(final TomikDistinguishedAdvokist card) { + super(card); + } + + @Override + public TomikDistinguishedAdvokist copy() { + return new TomikDistinguishedAdvokist(this); + } +} + +class TargetedByOpponentsPredicate implements Predicate { + + private final UUID sourceId; + + public TargetedByOpponentsPredicate(UUID sourceId) { + this.sourceId = sourceId; + } + + @Override + public boolean apply(MageObject input, Game game) { + StackObject stackObject = game.getStack().getStackObject(input.getId()); + Permanent source = game.getPermanentOrLKIBattlefield(this.sourceId); + if (stackObject != null && source != null) { + Player controller = game.getPlayer(source.getControllerId()); + return controller != null && game.isOpponent(controller, stackObject.getControllerId()); + } + return false; + } + + @Override + public String toString() { + return "targeted spells or abilities your opponents control"; + } +} + +class TomikDistinguishedAdvokistTargetEffect extends ContinuousRuleModifyingEffectImpl { + + TomikDistinguishedAdvokistTargetEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "and land cards in graveyards can't be the targets of spells or abilities your opponents control"; + } + + private TomikDistinguishedAdvokistTargetEffect(final TomikDistinguishedAdvokistTargetEffect effect) { + super(effect); + } + + @Override + public TomikDistinguishedAdvokistTargetEffect copy() { + return new TomikDistinguishedAdvokistTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGET; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Card targetCard = game.getCard(event.getTargetId()); + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + Player player = game.getPlayer(source.getControllerId()); + return targetCard != null && stackObject != null && player != null + && player.hasOpponent(stackObject.getControllerId(), game) + && game.getState().getZone(targetCard.getId()) == Zone.GRAVEYARD + && targetCard.isLand(); + } +} + +class TomikDistinguishedAdvokistRestrictionEffect extends ContinuousRuleModifyingEffectImpl { + + TomikDistinguishedAdvokistRestrictionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + this.staticText = "Your opponents can't play land cards from graveyards"; + } + + private TomikDistinguishedAdvokistRestrictionEffect(final TomikDistinguishedAdvokistRestrictionEffect effect) { + super(effect); + } + + @Override + public TomikDistinguishedAdvokistRestrictionEffect copy() { + return new TomikDistinguishedAdvokistRestrictionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PLAY_LAND; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.hasOpponent(event.getPlayerId(), game) + && game.getState().getZone(event.getSourceId()) == Zone.GRAVEYARD; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ToppleTheStatue.java b/Mage.Sets/src/mage/cards/t/ToppleTheStatue.java new file mode 100644 index 0000000000..f00e5f258d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ToppleTheStatue.java @@ -0,0 +1,68 @@ +package mage.cards.t; + +import mage.abilities.Ability; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ToppleTheStatue extends CardImpl { + + public ToppleTheStatue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Tap target permanent. If it's an artifact, destroy it. + // Draw a card. + this.getSpellAbility().addEffect(new ToppleTheStatueEffect()); + this.getSpellAbility().addTarget(new TargetPermanent()); + } + + private ToppleTheStatue(final ToppleTheStatue card) { + super(card); + } + + @Override + public ToppleTheStatue copy() { + return new ToppleTheStatue(this); + } +} + +class ToppleTheStatueEffect extends OneShotEffect { + + ToppleTheStatueEffect() { + super(Outcome.Benefit); + staticText = "Tap target permanent. If it's an artifact, destroy it.
Draw a card."; + } + + private ToppleTheStatueEffect(final ToppleTheStatueEffect effect) { + super(effect); + } + + @Override + public ToppleTheStatueEffect copy() { + return new ToppleTheStatueEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + permanent.tap(game); + if (permanent.isArtifact()) { + permanent.destroy(source.getSourceId(), game, false); + } + return new DrawCardSourceControllerEffect(1).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TransmuteArtifact.java b/Mage.Sets/src/mage/cards/t/TransmuteArtifact.java index 416828bc92..5c781ad298 100644 --- a/Mage.Sets/src/mage/cards/t/TransmuteArtifact.java +++ b/Mage.Sets/src/mage/cards/t/TransmuteArtifact.java @@ -78,7 +78,7 @@ class TransmuteArtifactEffect extends SearchEffect { return true; } //If you do, search your library for an artifact card. - if (sacrifice && controller.searchLibrary(target, game)) { + if (sacrifice && controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { Card card = controller.getLibrary().getCard(cardId, game); diff --git a/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java b/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java index 0fc284773f..bf4c0b41ea 100644 --- a/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java +++ b/Mage.Sets/src/mage/cards/t/TraverseTheOutlands.java @@ -78,7 +78,7 @@ class TraverseTheOutlandsEffect extends OneShotEffect { } TargetCardInLibrary target = new TargetCardInLibrary(0, amount, StaticFilters.FILTER_CARD_BASIC_LAND); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); } controller.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/t/TreasureNabber.java b/Mage.Sets/src/mage/cards/t/TreasureNabber.java index b851dbd574..ba23895c9d 100644 --- a/Mage.Sets/src/mage/cards/t/TreasureNabber.java +++ b/Mage.Sets/src/mage/cards/t/TreasureNabber.java @@ -1,21 +1,12 @@ package mage.cards.t; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.ContinuousEffectImpl; 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.PhaseStep; -import mage.constants.SubLayer; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; @@ -23,8 +14,10 @@ import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTargets; +import java.util.List; +import java.util.UUID; + /** - * * @author spjspj */ public final class TreasureNabber extends CardImpl { @@ -94,18 +87,8 @@ class TreasureNabberEffect extends ContinuousEffectImpl { protected FixedTargets fixedTargets; TreasureNabberEffect() { - super(Duration.Custom, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); + super(Duration.UntilEndOfYourNextTurn, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); this.staticText = "gain control of that artifact until the end of your next turn"; - startingTurn = 0; - } - - @Override - public void init(Ability source, Game game) { - super.init(source, game); - startingTurn = game.getTurnNum(); - if (game.getPhase().getStep().getType() == PhaseStep.END_TURN) { - startingTurn = game.getTurnNum() + 1; - } } TreasureNabberEffect(final TreasureNabberEffect effect) { @@ -118,16 +101,6 @@ class TreasureNabberEffect extends ContinuousEffectImpl { return new TreasureNabberEffect(this); } - @Override - public boolean isInactive(Ability source, Game game) { - if (startingTurn != 0 && game.getTurnNum() >= startingTurn && game.getPhase().getStep().getType() == PhaseStep.END_TURN) { - if (game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; - } - @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); diff --git a/Mage.Sets/src/mage/cards/t/TrustedPegasus.java b/Mage.Sets/src/mage/cards/t/TrustedPegasus.java new file mode 100644 index 0000000000..612d7f6fde --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrustedPegasus.java @@ -0,0 +1,62 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +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.common.FilterAttackingCreature; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TrustedPegasus extends CardImpl { + + private static final FilterPermanent filter + = new FilterAttackingCreature("attacking creature without flying"); + + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public TrustedPegasus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.PEGASUS); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Trusted Pegasus attacks, target attacking creature without flying gains flying until end of turn. + Ability ability = new AttacksTriggeredAbility( + new GainAbilityTargetEffect( + FlyingAbility.getInstance(), + Duration.EndOfTurn + ), false + ); + ability.addTarget(new TargetPermanent(filter)); + addAbility(ability); + } + + private TrustedPegasus(final TrustedPegasus card) { + super(card); + } + + @Override + public TrustedPegasus copy() { + return new TrustedPegasus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TurretOgre.java b/Mage.Sets/src/mage/cards/t/TurretOgre.java new file mode 100644 index 0000000000..c632896837 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TurretOgre.java @@ -0,0 +1,61 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.filter.predicate.permanent.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TurretOgre extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(AnotherPredicate.instance); + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public TurretOgre(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.OGRE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When Turret Ogre enters the battlefield, if you control another creature with power 4 or greater, Turret Ogre deals 2 damage to each opponent. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DamagePlayersEffect(2, TargetController.OPPONENT)), + new PermanentsOnTheBattlefieldCondition(filter), "When {this} enters the battlefield, " + + "if you control another creature with power 4 or greater, {this} deals 2 damage to each opponent." + )); + } + + private TurretOgre(final TurretOgre card) { + super(card); + } + + @Override + public TurretOgre copy() { + return new TurretOgre(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TyrantsScorn.java b/Mage.Sets/src/mage/cards/t/TyrantsScorn.java new file mode 100644 index 0000000000..0b082c9478 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TyrantsScorn.java @@ -0,0 +1,52 @@ +package mage.cards.t; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TyrantsScorn extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature with converted mana cost 3 or less"); + + static { + filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, 4)); + } + + public TyrantsScorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{B}"); + + // Choose one — + // • Destroy target creature with converted mana cost 3 or less. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + // • Return target creature to its owner's hand. + Mode mode = new Mode(new ReturnToHandTargetEffect()); + mode.addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addMode(mode); + } + + private TyrantsScorn(final TyrantsScorn card) { + super(card); + } + + @Override + public TyrantsScorn copy() { + return new TyrantsScorn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UginTheIneffable.java b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java new file mode 100644 index 0000000000..18a511fffa --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java @@ -0,0 +1,216 @@ +package mage.cards.u; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorlessPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.UginTheIneffableToken; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; + +import static mage.constants.Outcome.Benefit; + +/** + * @author TheElk801 + */ +public final class UginTheIneffable extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + private static final FilterPermanent filter2 = new FilterPermanent("permanent that's one or more colors"); + + static { + filter.add(ColorlessPredicate.instance); + filter2.add(Predicates.not(ColorlessPredicate.instance)); + } + + public UginTheIneffable(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{6}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.UGIN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // Colorless spells you cast cost {2} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect( + filter, 2 + ).setText("Colorless spells you cast cost {2} less to cast."))); + + // +1: Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand. + this.addAbility(new LoyaltyAbility(new UginTheIneffableEffect(), 1)); + + // -3: Destroy target permanent that's one or more colors. + Ability ability = new LoyaltyAbility(new DestroyTargetEffect(), -3); + ability.addTarget(new TargetPermanent(filter2)); + this.addAbility(ability); + } + + private UginTheIneffable(final UginTheIneffable card) { + super(card); + } + + @Override + public UginTheIneffable copy() { + return new UginTheIneffable(this); + } +} + +class UginTheIneffableEffect extends OneShotEffect { + + UginTheIneffableEffect() { + super(Benefit); + staticText = "Exile the top card of your library face down and look at it. " + + "Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, " + + "put the exiled card into your hand."; + } + + private UginTheIneffableEffect(final UginTheIneffableEffect effect) { + super(effect); + } + + @Override + public UginTheIneffableEffect copy() { + return new UginTheIneffableEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + player.lookAtCards(sourcePermanent.getIdName(), card, game); + player.moveCards(card, Zone.EXILED, source, game); + card.turnFaceDown(game, source.getControllerId()); + Set tokenObjs = new HashSet<>(); + CreateTokenEffect effect = new CreateTokenEffect(new UginTheIneffableToken()); + effect.apply(game, source); + for (UUID addedTokenId : effect.getLastAddedTokenIds()) { + + // display referenced exiled face-down card on token + SimpleStaticAbility sa = new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect("Referenced object: " + + card.getId().toString().substring(0, 3))); + GainAbilityTargetEffect gainAbilityEffect = new GainAbilityTargetEffect(sa, Duration.WhileOnBattlefield); + gainAbilityEffect.setTargetPointer(new FixedTarget(addedTokenId)); + game.addEffect(gainAbilityEffect, source); + + // look at face-down card in exile + UginTheIneffableLookAtFaceDownEffect lookAtEffect = new UginTheIneffableLookAtFaceDownEffect(); + lookAtEffect.setTargetPointer(new FixedTarget(card.getId())); + game.addEffect(lookAtEffect, source); + + tokenObjs.add(new MageObjectReference(addedTokenId, game)); + game.addDelayedTriggeredAbility(new UginTheIneffableDelayedTriggeredAbility( + tokenObjs, new MageObjectReference(card, game) + ), source); + } + return true; + } +} + +class UginTheIneffableDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private final Set tokenRefs; + private final MageObjectReference cardRef; + + UginTheIneffableDelayedTriggeredAbility(Set tokens, MageObjectReference card) { + super(null, Duration.Custom, true); + this.tokenRefs = tokens; + this.cardRef = card; + } + + private UginTheIneffableDelayedTriggeredAbility(final UginTheIneffableDelayedTriggeredAbility ability) { + super(ability); + this.tokenRefs = ability.tokenRefs; + this.cardRef = ability.cardRef; + } + + @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.getToZone() == Zone.BATTLEFIELD + || tokenRefs.stream().noneMatch(tokenRef -> tokenRef.refersTo(zEvent.getTarget(), game))) { + return false; + } + this.getEffects().clear(); + Effect effect = new ReturnToHandTargetEffect(); + effect.setTargetPointer(new FixedTarget(cardRef)); + this.addEffect(effect); + return true; + } + + @Override + public UginTheIneffableDelayedTriggeredAbility copy() { + return new UginTheIneffableDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When this token leaves the battlefield, put the exiled card into your hand."; + } +} + +class UginTheIneffableLookAtFaceDownEffect extends AsThoughEffectImpl { + + UginTheIneffableLookAtFaceDownEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + } + + private UginTheIneffableLookAtFaceDownEffect(final UginTheIneffableLookAtFaceDownEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public UginTheIneffableLookAtFaceDownEffect copy() { + return new UginTheIneffableLookAtFaceDownEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); + } + return affectedControllerId.equals(source.getControllerId()) + && objectId.equals(cardId) + && game.getState().getExile().containsId(cardId, game); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UginsConjurant.java b/Mage.Sets/src/mage/cards/u/UginsConjurant.java new file mode 100644 index 0000000000..a633fbff27 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UginsConjurant.java @@ -0,0 +1,45 @@ +package mage.cards.u; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.effects.PreventDamageAndRemoveCountersEffect; +import mage.abilities.common.SimpleStaticAbility; +import mage.counters.CounterType; +import mage.constants.Zone; + +/** + * + * @author antoni-g + */ +public final class UginsConjurant extends CardImpl { + + public UginsConjurant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}"); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.MONK); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Ugin’s Conjurant enters the battlefield with X +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))); + // If damage would be dealt to Ugin’s Conjurant while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from Ugin’s Conjurant. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PreventDamageAndRemoveCountersEffect())); + } + + private UginsConjurant(final UginsConjurant card) { + super(card); + } + + @Override + public UginsConjurant copy() { + return new UginsConjurant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java b/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java index a002a14dce..840fbbe22e 100644 --- a/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java @@ -75,7 +75,7 @@ class UncageTheMenagerieEffect extends OneShotEffect { int xValue = source.getManaCostsToPay().getX(); UncageTheMenagerieTarget target = new UncageTheMenagerieTarget(xValue); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage.Sets/src/mage/cards/u/UnlikelyAid.java b/Mage.Sets/src/mage/cards/u/UnlikelyAid.java new file mode 100644 index 0000000000..39f4478ca8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnlikelyAid.java @@ -0,0 +1,40 @@ +package mage.cards.u; + +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnlikelyAid extends CardImpl { + + public UnlikelyAid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Target creature gets +2/+0 and gains indestructible until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect( + 2, 0, Duration.EndOfTurn + ).setText("Target creature gets +2/+0")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains indestructable until end of turn")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private UnlikelyAid(final UnlikelyAid card) { + super(card); + } + + @Override + public UnlikelyAid copy() { + return new UnlikelyAid(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UrborgPanther.java b/Mage.Sets/src/mage/cards/u/UrborgPanther.java new file mode 100644 index 0000000000..657d34d585 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UrborgPanther.java @@ -0,0 +1,81 @@ +package mage.cards.u; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ColoredManaCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ColoredManaSymbol; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.permanent.BlockingAttackerIdPredicate; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Ketsuban + */ +public class UrborgPanther extends CardImpl { + + private static final FilterControlledCreaturePermanent filter1 = new FilterControlledCreaturePermanent("creature named Feral Shadow"); + private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("creature named Breathstealer"); + + private static final FilterCard filterCard = new FilterCreatureCard("card named Spirit of the Night"); + + static { + filter1.add(new NamePredicate("Feral Shadow")); + filter2.add(new NamePredicate("Breathstealer")); + filterCard.add(new NamePredicate("Spirit of the Night")); + } + + public UrborgPanther(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{2}{B}"); + this.subtype.add(SubType.NIGHTSTALKER); + this.subtype.add(SubType.CAT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // B, Sacrifice Urborg Panther: Destroy target creature blocking Urborg Panther. + Ability ability1 = new SimpleActivatedAbility(new DestroyTargetEffect(), + new ColoredManaCost(ColoredManaSymbol.B)); + ability1.addCost(new SacrificeSourceCost()); + FilterCreaturePermanent filter = new FilterCreaturePermanent("creature blocking {this}"); + filter.add(new BlockingAttackerIdPredicate(this.getId())); + ability1.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability1); + + // Sacrifice a creature named Feral Shadow, a creature named Breathstealer, and + // Urborg Panther: Search your library for a card named Spirit of the Night and + // put that card onto the battlefield. Then shuffle your library. + Ability ability2 = new SimpleActivatedAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(1, 1, new FilterCard(filterCard))), + new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, filter1, true))); + ability2.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, filter2, true))); + ability2.addCost(new SacrificeSourceCost()); + this.addAbility(ability2); + } + + public UrborgPanther(final UrborgPanther card) { + super(card); + } + + @Override + public UrborgPanther copy() { + return new UrborgPanther(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/v/VampireOpportunist.java b/Mage.Sets/src/mage/cards/v/VampireOpportunist.java new file mode 100644 index 0000000000..d515ad02bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VampireOpportunist.java @@ -0,0 +1,44 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VampireOpportunist extends CardImpl { + + public VampireOpportunist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {6}{B}: Each opponent loses 2 life and you gain 2 life. + Ability ability = new SimpleActivatedAbility( + new LoseLifeOpponentsEffect(2), new ManaCostsImpl("{6}{B}") + ); + ability.addEffect(new GainLifeEffect(2).concatBy("and")); + this.addAbility(ability); + } + + private VampireOpportunist(final VampireOpportunist card) { + super(card); + } + + @Override + public VampireOpportunist copy() { + return new VampireOpportunist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java index 9e0f3ee887..cd0e14e0e4 100644 --- a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java +++ b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java @@ -127,7 +127,7 @@ class VerdantSuccessionEffect extends OneShotEffect { FilterCard filterCard = new FilterCard("Card named " + permanent.getName()); filterCard.add(new NamePredicate(permanent.getName())); TargetCardInLibrary target = new TargetCardInLibrary(filterCard); - controller.searchLibrary(target, game); + controller.searchLibrary(target, source, game); if (!target.getTargets().isEmpty()) { Card card = game.getCard(target.getFirstTarget()); if (card != null diff --git a/Mage.Sets/src/mage/cards/v/VeteranBodyguard.java b/Mage.Sets/src/mage/cards/v/VeteranBodyguard.java index d1442674c9..483b834f2b 100644 --- a/Mage.Sets/src/mage/cards/v/VeteranBodyguard.java +++ b/Mage.Sets/src/mage/cards/v/VeteranBodyguard.java @@ -1,20 +1,17 @@ - package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceTappedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.decorator.ConditionalPreventionEffect; +import mage.abilities.effects.PreventionEffectImpl; 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.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.UnblockedPredicate; @@ -24,8 +21,9 @@ import mage.game.events.DamagePlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author MTGfan */ public final class VeteranBodyguard extends CardImpl { @@ -38,7 +36,11 @@ public final class VeteranBodyguard extends CardImpl { this.toughness = new MageInt(5); // As long as Veteran Bodyguard is untapped, all damage that would be dealt to you by unblocked creatures is dealt to Veteran Bodyguard instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new VeteranBodyguardEffect(), new InvertCondition(SourceTappedCondition.instance), "As long as {this} is untapped, all damage that would be dealt to you by unblocked creatures is dealt to {this} instead."))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalPreventionEffect( + new VeteranBodyguardEffect(), + new InvertCondition(SourceTappedCondition.instance), + "As long as {this} is untapped, all damage that would be dealt to you by unblocked creatures is dealt to {this} instead." + ))); } public VeteranBodyguard(final VeteranBodyguard card) { @@ -51,7 +53,7 @@ public final class VeteranBodyguard extends CardImpl { } } -class VeteranBodyguardEffect extends ReplacementEffectImpl { +class VeteranBodyguardEffect extends PreventionEffectImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("unblocked creatures"); @@ -60,7 +62,7 @@ class VeteranBodyguardEffect extends ReplacementEffectImpl { } VeteranBodyguardEffect() { - super(Duration.WhileOnBattlefield, Outcome.RedirectDamage); + super(Duration.WhileOnBattlefield); staticText = "all combat damage that would be dealt to you by unblocked creatures is dealt to {source} instead"; } @@ -76,7 +78,7 @@ class VeteranBodyguardEffect extends ReplacementEffectImpl { permanent.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable()); return true; } - return true; + return false; } @Override diff --git a/Mage.Sets/src/mage/cards/v/VeteranExplorer.java b/Mage.Sets/src/mage/cards/v/VeteranExplorer.java index 81b84fa827..0e34e7fb8c 100644 --- a/Mage.Sets/src/mage/cards/v/VeteranExplorer.java +++ b/Mage.Sets/src/mage/cards/v/VeteranExplorer.java @@ -91,7 +91,7 @@ class VeteranExplorerEffect extends OneShotEffect { if (player.chooseUse(Outcome.PutCardInPlay, "Search your library for up to two basic land cards and put them onto the battlefield?", source, game)) { usingPlayers.add(player); TargetCardInLibrary target = new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LAND); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/v/ViralDrake.java b/Mage.Sets/src/mage/cards/v/ViralDrake.java index 251ae89246..ea734a75cb 100644 --- a/Mage.Sets/src/mage/cards/v/ViralDrake.java +++ b/Mage.Sets/src/mage/cards/v/ViralDrake.java @@ -1,7 +1,5 @@ - package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -14,21 +12,27 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import java.util.UUID; + /** - * * @author North */ public final class ViralDrake extends CardImpl { public ViralDrake(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); this.subtype.add(SubType.DRAKE); this.power = new MageInt(1); this.toughness = new MageInt(4); + // Flying this.addAbility(FlyingAbility.getInstance()); + + // Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.) this.addAbility(InfectAbility.getInstance()); + + // {3}{U}: Proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ProliferateEffect(), new ManaCostsImpl("{3}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java b/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java new file mode 100644 index 0000000000..f08781e6df --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java @@ -0,0 +1,210 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VivienChampionOfTheWilds extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature spells"); + + static { + filter.add(new CardTypePredicate(CardType.CREATURE)); + } + + public VivienChampionOfTheWilds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.VIVIEN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // You may cast creature spells as though they had flash. + this.addAbility(new SimpleStaticAbility(new CastAsThoughItHadFlashAllEffect( + Duration.WhileOnBattlefield, filter + ))); + + // +1: Until your next turn, up to one target creature gains vigilance and reach. + Ability ability = new LoyaltyAbility(new GainAbilityTargetEffect( + VigilanceAbility.getInstance(), Duration.UntilYourNextTurn + ).setText("Until your next turn, up to one target creature gains vigilance"), 1); + ability.addEffect(new GainAbilityTargetEffect( + ReachAbility.getInstance(), Duration.UntilYourNextTurn + ).setText("and reach")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // -2: Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. + // For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card. + this.addAbility(new LoyaltyAbility(new VivienChampionOfTheWildsEffect(), -2)); + } + + private VivienChampionOfTheWilds(final VivienChampionOfTheWilds card) { + super(card); + } + + @Override + public VivienChampionOfTheWilds copy() { + return new VivienChampionOfTheWilds(this); + } +} + +class VivienChampionOfTheWildsEffect extends OneShotEffect { + + VivienChampionOfTheWildsEffect() { + super(Outcome.Benefit); + staticText = "Look at the top three cards of your library. " + + "Exile one face down and put the rest on the bottom of your library in any order. " + + "For as long as it remains exiled, you may look at that card " + + "and you may cast it if it's a creature card."; + } + + private VivienChampionOfTheWildsEffect(final VivienChampionOfTheWildsEffect effect) { + super(effect); + } + + @Override + public VivienChampionOfTheWildsEffect copy() { + return new VivienChampionOfTheWildsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + + // select + Cards cardsToLook = new CardsImpl(player.getLibrary().getTopCards(game, 3)); + FilterCard filter = new FilterCard("card to exile face down"); + TargetCard target = new TargetCardInLibrary(filter); + if (!player.choose(outcome, cardsToLook, target, game)) { + return false; + } + + // exile + Card cardToExile = game.getCard(target.getFirstTarget()); + if (!player.moveCardsToExile(cardToExile, source, game, false, + CardUtil.getCardExileZoneId(game, source), + CardUtil.createObjectRealtedWindowTitle(source, game, " (look and cast)"))) { + return false; + } + cardToExile.setFaceDown(true, game); + + // look and cast + ContinuousEffect effect = new VivienChampionOfTheWildsLookEffect(player.getId()); + effect.setTargetPointer(new FixedTarget(cardToExile, game)); + game.addEffect(effect, source); + if (cardToExile.isCreature()) { + effect = new VivienChampionOfTheWildsCastFromExileEffect(player.getId()); + effect.setTargetPointer(new FixedTarget(cardToExile, game)); + game.addEffect(effect, source); + } + + // put the rest on the bottom of your library in any order + Cards cardsToBottom = new CardsImpl(cardsToLook); + cardsToBottom.remove(cardToExile); + player.putCardsOnBottomOfLibrary(cardsToBottom, game, source, true); + + return true; + } +} + +class VivienChampionOfTheWildsLookEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + VivienChampionOfTheWildsLookEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + } + + private VivienChampionOfTheWildsLookEffect(final VivienChampionOfTheWildsLookEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public VivienChampionOfTheWildsLookEffect copy() { + return new VivienChampionOfTheWildsLookEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + } + return affectedControllerId.equals(authorizedPlayerId) + && objectId.equals(cardId); + } +} + +class VivienChampionOfTheWildsCastFromExileEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + VivienChampionOfTheWildsCastFromExileEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + } + + private VivienChampionOfTheWildsCastFromExileEffect(final VivienChampionOfTheWildsCastFromExileEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public VivienChampionOfTheWildsCastFromExileEffect copy() { + return new VivienChampionOfTheWildsCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + } else if (objectId.equals(cardId) + && affectedControllerId.equals(authorizedPlayerId)) { + Card card = game.getCard(objectId); + // TODO: Allow to cast Zoetic Cavern face down + return card != null && !card.isLand(); + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/v/VizierOfRemedies.java b/Mage.Sets/src/mage/cards/v/VizierOfRemedies.java index 2823ffa75b..29e2cd9952 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfRemedies.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfRemedies.java @@ -1,24 +1,19 @@ - package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; 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.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; +import java.util.UUID; + /** - * * @author Stravant */ public final class VizierOfRemedies extends CardImpl { @@ -64,7 +59,7 @@ class VizierOfRemediesReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() - 1); + event.setAmountForCounters(event.getAmount() - 1, true); return false; } @@ -76,11 +71,9 @@ class VizierOfRemediesReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { if (source != null && source.getControllerId() != null) { - if (source.isControlledBy(game.getControllerId(event.getTargetId())) + return source.isControlledBy(game.getControllerId(event.getTargetId())) && event.getData() != null && event.getData().equals(CounterType.M1M1.getName()) - && event.getAmount() > 0) { - return true; - } + && event.getAmount() > 0; } return false; } diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheAnointed.java b/Mage.Sets/src/mage/cards/v/VizierOfTheAnointed.java index 34df5d8b55..a65c5e5d5d 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheAnointed.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheAnointed.java @@ -150,7 +150,7 @@ class SearchLibraryPutInGraveyard extends SearchEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java index 4df002baa7..a51a1e68b5 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java @@ -7,7 +7,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughManaEffect; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,7 +31,7 @@ public final class VizierOfTheMenagerie extends CardImpl { this.toughness = new MageInt(4); // You may look at the top card of your library. (You may do this at any time.) - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VizierOfTheMenagerieTopCardRevealedEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast the top card of your library if it's a creature card. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VizierOfTheMenagerieTopCardCastEffect())); @@ -51,38 +51,6 @@ public final class VizierOfTheMenagerie extends CardImpl { } } -class VizierOfTheMenagerieTopCardRevealedEffect extends ContinuousEffectImpl { - - public VizierOfTheMenagerieTopCardRevealedEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - staticText = "You may look at the top card of your library any time"; - } - - public VizierOfTheMenagerieTopCardRevealedEffect(final VizierOfTheMenagerieTopCardRevealedEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Card topCard = controller.getLibrary().getFromTop(game); - if (topCard != null) { - MageObject vizierOfTheMenagerie = source.getSourceObject(game); - if (vizierOfTheMenagerie != null) { - controller.lookAtCards("Top card of " + vizierOfTheMenagerie.getIdName() + " controller's library", topCard, game); - } - } - } - return true; - } - - @Override - public VizierOfTheMenagerieTopCardRevealedEffect copy() { - return new VizierOfTheMenagerieTopCardRevealedEffect(this); - } -} - class VizierOfTheMenagerieTopCardCastEffect extends AsThoughEffectImpl { public VizierOfTheMenagerieTopCardCastEffect() { diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheScorpion.java b/Mage.Sets/src/mage/cards/v/VizierOfTheScorpion.java new file mode 100644 index 0000000000..fba4ca9269 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheScorpion.java @@ -0,0 +1,55 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.AmassEffect; +import mage.abilities.keyword.DeathtouchAbility; +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.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VizierOfTheScorpion extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.ZOMBIE, "Zombie tokens"); + + static { + filter.add(TokenPredicate.instance); + } + + public VizierOfTheScorpion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Vizier of the Scorpion enters the battlefield, amass 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AmassEffect(1))); + + // Zombie tokens you control have deathtouch. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private VizierOfTheScorpion(final VizierOfTheScorpion card) { + super(card); + } + + @Override + public VizierOfTheScorpion copy() { + return new VizierOfTheScorpion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VoltCharge.java b/Mage.Sets/src/mage/cards/v/VoltCharge.java index 06cf552162..4a711c47c5 100644 --- a/Mage.Sets/src/mage/cards/v/VoltCharge.java +++ b/Mage.Sets/src/mage/cards/v/VoltCharge.java @@ -1,7 +1,5 @@ - package mage.cards.v; -import java.util.UUID; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.cards.CardImpl; @@ -9,19 +7,20 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** - * * @author North */ public final class VoltCharge extends CardImpl { public VoltCharge(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); - - this.getSpellAbility().addTarget(new TargetAnyTarget()); + // Volt Charge deals 3 damage to any target. Proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.) this.getSpellAbility().addEffect(new DamageTargetEffect(3)); this.getSpellAbility().addEffect(new ProliferateEffect()); + this.getSpellAbility().addTarget(new TargetAnyTarget()); } public VoltCharge(final VoltCharge card) { diff --git a/Mage.Sets/src/mage/cards/v/VonasHunger.java b/Mage.Sets/src/mage/cards/v/VonasHunger.java index 3d4c435349..ce01560273 100644 --- a/Mage.Sets/src/mage/cards/v/VonasHunger.java +++ b/Mage.Sets/src/mage/cards/v/VonasHunger.java @@ -8,6 +8,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SacrificeOpponentsEffect; import mage.abilities.effects.keyword.AscendEffect; import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.PermanentsYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -32,6 +33,8 @@ public final class VonasHunger extends CardImpl { // Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.) this.getSpellAbility().addEffect(new AscendEffect()); + this.getSpellAbility().addHint(CitysBlessingHint.instance); + this.getSpellAbility().addHint(PermanentsYouControlHint.instance); // Each opponent sacrifices a creature. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( @@ -43,7 +46,6 @@ public final class VonasHunger extends CardImpl { new VonasHungerEffect(), CitysBlessingCondition.instance, "If you have the city's blessing, instead each opponent sacrifices half the creatures he or she controls rounded up")); - this.getSpellAbility().addHint(CitysBlessingHint.instance); } public VonasHunger(final VonasHunger card) { diff --git a/Mage.Sets/src/mage/cards/v/VraskaSwarmsEminence.java b/Mage.Sets/src/mage/cards/v/VraskaSwarmsEminence.java index e1a7d3f2a5..3dc008f4ed 100644 --- a/Mage.Sets/src/mage/cards/v/VraskaSwarmsEminence.java +++ b/Mage.Sets/src/mage/cards/v/VraskaSwarmsEminence.java @@ -1,31 +1,35 @@ package mage.cards.v; +import java.util.UUID; import mage.abilities.LoyaltyAbility; -import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; 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.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.game.permanent.token.AssassinToken2; - -import java.util.UUID; +import mage.target.targetpointer.FixedTarget; /** * @author TheElk801 */ public final class VraskaSwarmsEminence extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature you control with deathtouch"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature you control with deathtouch"); static { filter.add(new AbilityPredicate(DeathtouchAbility.class)); @@ -39,12 +43,9 @@ public final class VraskaSwarmsEminence extends CardImpl { this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(5)); // Whenever a creature you control with deathtouch deals damage to a player or planeswalker, put a +1/+1 counter on that creature. - // TODO: make this trigger on planeswalkers - this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( - new AddCountersTargetEffect( - CounterType.P1P1.createInstance() - ).setText("put a +1/+1 counter on that creature"), - filter, false, SetTargetPointer.PERMANENT, false) + this.addAbility(new VraskaSwarmsEminenceTriggeredAbility(Zone.BATTLEFIELD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setText("put a +1/+1 counter on that creature"), + filter) ); // -2: Create a 1/1 black Assassin creature token with deathtouch and "Whenever this creature deals damage to a planeswalker, destroy that planeswalker." @@ -60,3 +61,49 @@ public final class VraskaSwarmsEminence extends CardImpl { return new VraskaSwarmsEminence(this); } } + +class VraskaSwarmsEminenceTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterPermanent filter; + + public VraskaSwarmsEminenceTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter) { + super(zone, effect, false); + this.filter = filter; + } + + public VraskaSwarmsEminenceTriggeredAbility(final VraskaSwarmsEminenceTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public VraskaSwarmsEminenceTriggeredAbility copy() { + return new VraskaSwarmsEminenceTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER + || event.getType() == GameEvent.EventType.DAMAGED_PLANESWALKER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) { + for (Effect effect : this.getEffects()) { + effect.setValue("damage", event.getAmount()); + effect.setValue("sourceId", event.getSourceId()); + effect.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game))); + } + return true; + } + return false; + } + + @Override + public String getRule() { + return "Whenever a creature you control with deathtouch deals damage to a player or planeswalker," + super.getRule(); + } + +} diff --git a/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java b/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java index 343271b27d..172a230eaa 100644 --- a/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java +++ b/Mage.Sets/src/mage/cards/v/VraskaTheUnseen.java @@ -1,7 +1,5 @@ - package mage.cards.v; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.TriggeredAbilityImpl; @@ -12,14 +10,7 @@ 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.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedPlaneswalkerEvent; import mage.game.events.GameEvent; @@ -29,15 +20,15 @@ import mage.game.permanent.token.AssassinToken; import mage.target.common.TargetNonlandPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * If an effect creates a copy of one of the Assassin creature tokens, the copy * will also have the triggered ability. - * + *

* Each Assassin token's triggered ability will trigger whenever it deals combat * damage to any player, including you. * - * * @author LevelX2 */ public final class VraskaTheUnseen extends CardImpl { @@ -79,7 +70,6 @@ class VraskaTheUnseenGainAbilityEffect extends ContinuousEffectImpl { super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.ability = ability; staticText = "Until your next turn, whenever a creature deals combat damage to {this}, destroy that creature"; - startingTurn = 0; } public VraskaTheUnseenGainAbilityEffect(final VraskaTheUnseenGainAbilityEffect effect) { @@ -87,12 +77,6 @@ class VraskaTheUnseenGainAbilityEffect extends ContinuousEffectImpl { this.ability = effect.ability.copy(); } - @Override - public void init(Ability source, Game game) { - super.init(source, game); //To change body of generated methods, choose Tools | Templates. - startingTurn = game.getTurnNum(); - } - @Override public VraskaTheUnseenGainAbilityEffect copy() { return new VraskaTheUnseenGainAbilityEffect(this); @@ -110,12 +94,7 @@ class VraskaTheUnseenGainAbilityEffect extends ContinuousEffectImpl { @Override public boolean isInactive(Ability source, Game game) { - if (startingTurn != 0 && game.getTurnNum() != startingTurn) { - if (game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); } } diff --git a/Mage.Sets/src/mage/cards/w/WallOfDust.java b/Mage.Sets/src/mage/cards/w/WallOfDust.java index f7517a38ad..1a1c64b7fa 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfDust.java +++ b/Mage.Sets/src/mage/cards/w/WallOfDust.java @@ -47,7 +47,6 @@ public final class WallOfDust extends CardImpl { class WallOfDustRestrictionEffect extends RestrictionEffect { - int nextTurnTargetController = 0; protected MageObjectReference targetPermanentReference; public WallOfDustRestrictionEffect() { @@ -57,7 +56,6 @@ class WallOfDustRestrictionEffect extends RestrictionEffect { public WallOfDustRestrictionEffect(final WallOfDustRestrictionEffect effect) { super(effect); - this.nextTurnTargetController = effect.nextTurnTargetController; this.targetPermanentReference = effect.targetPermanentReference; } @@ -68,26 +66,22 @@ class WallOfDustRestrictionEffect extends RestrictionEffect { @Override public boolean isInactive(Ability source, Game game) { - if (targetPermanentReference == null) { + if (targetPermanentReference == null || targetPermanentReference.getPermanent(game) == null) { return true; } - Permanent targetPermanent = targetPermanentReference.getPermanent(game); - if (targetPermanent == null) { - return true; - } - if (nextTurnTargetController == 0 && startingTurn != game.getTurnNum() && game.isActivePlayer(targetPermanent.getControllerId())) { - nextTurnTargetController = game.getTurnNum(); - } - return game.getPhase().getType() == TurnPhase.END && nextTurnTargetController > 0 && game.getTurnNum() > nextTurnTargetController; + + return game.getPhase().getType() == TurnPhase.END && this.isYourNextTurn(game); } @Override public void init(Ability source, Game game) { super.init(source, game); - if (getTargetPointer().getFirst(game, source) == null) { - discard(); + Permanent perm = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (perm != null) { + targetPermanentReference = new MageObjectReference(perm, game); + setStartingControllerAndTurnNum(game, perm.getControllerId(), game.getActivePlayerId()); } else { - targetPermanentReference = new MageObjectReference(getTargetPointer().getFirst(game, source), game); + discard(); } } diff --git a/Mage.Sets/src/mage/cards/w/WallOfRunes.java b/Mage.Sets/src/mage/cards/w/WallOfRunes.java new file mode 100644 index 0000000000..47957d38b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WallOfRunes.java @@ -0,0 +1,41 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WallOfRunes extends CardImpl { + + public WallOfRunes(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.subtype.add(SubType.WALL); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // When Wall of Runes enters the battlefield, scry 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1))); + } + + private WallOfRunes(final WallOfRunes card) { + super(card); + } + + @Override + public WallOfRunes copy() { + return new WallOfRunes(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WanderersStrike.java b/Mage.Sets/src/mage/cards/w/WanderersStrike.java index 1948747209..e707faed14 100644 --- a/Mage.Sets/src/mage/cards/w/WanderersStrike.java +++ b/Mage.Sets/src/mage/cards/w/WanderersStrike.java @@ -20,10 +20,7 @@ public final class WanderersStrike extends CardImpl { // Exile target creature, then proliferate. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new ProliferateEffect().setText( - "then proliferate (Choose any number of permanents and/or players, " + - "then give each another counter of each kind already there.)" - )); + this.getSpellAbility().addEffect(new ProliferateEffect().concatBy("then")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/w/WaveOfVitriol.java b/Mage.Sets/src/mage/cards/w/WaveOfVitriol.java index e29fbc5056..5b223ec862 100644 --- a/Mage.Sets/src/mage/cards/w/WaveOfVitriol.java +++ b/Mage.Sets/src/mage/cards/w/WaveOfVitriol.java @@ -100,7 +100,7 @@ class WaveOfVitriolEffect extends OneShotEffect { for (Map.Entry entry : sacrificedLands.entrySet()) { if (entry.getKey().chooseUse(Outcome.PutLandInPlay, "Search your library for up to " + entry.getValue() + " basic lands?", source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(0, entry.getValue(), StaticFilters.FILTER_CARD_BASIC_LAND); - if (entry.getKey().searchLibrary(target, game)) { + if (entry.getKey().searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { toBattlefield.addAll(target.getTargets()); playersToShuffle.add(entry.getKey()); diff --git a/Mage.Sets/src/mage/cards/w/WeirdHarvest.java b/Mage.Sets/src/mage/cards/w/WeirdHarvest.java index 4ee86327ac..ca1fa3a86f 100644 --- a/Mage.Sets/src/mage/cards/w/WeirdHarvest.java +++ b/Mage.Sets/src/mage/cards/w/WeirdHarvest.java @@ -88,7 +88,7 @@ class WeirdHarvestEffect extends OneShotEffect { if (player.chooseUse(Outcome.PutCardInPlay, "Search your library for up " + xValue + " creature cards and put them into your hand?", source, game)) { usingPlayers.add(player); TargetCardInLibrary target = new TargetCardInLibrary(0, xValue, new FilterCreatureCard()); - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(target.getTargets()); player.moveCards(cards, Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/w/WidespreadBrutality.java b/Mage.Sets/src/mage/cards/w/WidespreadBrutality.java index 7be9d81e31..b6dd33e1fb 100644 --- a/Mage.Sets/src/mage/cards/w/WidespreadBrutality.java +++ b/Mage.Sets/src/mage/cards/w/WidespreadBrutality.java @@ -56,13 +56,12 @@ class WidespreadBrutalityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { AmassEffect amassEffect = new AmassEffect(2); - if (!amassEffect.apply(game, source)) { - return false; - } + amassEffect.apply(game, source); Permanent amassedArmy = game.getPermanent(amassEffect.getAmassedCreatureId()); if (amassedArmy == null) { return false; } + game.applyEffects(); int power = amassedArmy.getPower().getValue(); for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { if (permanent != null && permanent.isCreature() && !permanent.hasSubtype(SubType.ARMY, game)) { diff --git a/Mage.Sets/src/mage/cards/w/WildPair.java b/Mage.Sets/src/mage/cards/w/WildPair.java index b832633bcc..6349ee4aed 100644 --- a/Mage.Sets/src/mage/cards/w/WildPair.java +++ b/Mage.Sets/src/mage/cards/w/WildPair.java @@ -82,7 +82,7 @@ class WildPairEffect extends OneShotEffect { FilterCreatureCard filter = new FilterCreatureCard("creature card with total power and toughness " + totalPT); filter.add(new TotalPowerAndToughnessPredicate(ComparisonType.EQUAL_TO, totalPT)); TargetCardInLibrary target = new TargetCardInLibrary(1, filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/w/WildResearch.java b/Mage.Sets/src/mage/cards/w/WildResearch.java index b4d7d9e6b5..2eb4936c37 100644 --- a/Mage.Sets/src/mage/cards/w/WildResearch.java +++ b/Mage.Sets/src/mage/cards/w/WildResearch.java @@ -82,7 +82,7 @@ class WildResearchEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); if (controller != null && sourceObject != null) { TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().remove(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/w/WindingConstrictor.java b/Mage.Sets/src/mage/cards/w/WindingConstrictor.java index 3d113229f5..c5db174c38 100644 --- a/Mage.Sets/src/mage/cards/w/WindingConstrictor.java +++ b/Mage.Sets/src/mage/cards/w/WindingConstrictor.java @@ -1,25 +1,20 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; 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.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class WindingConstrictor extends CardImpl { @@ -32,7 +27,7 @@ public final class WindingConstrictor extends CardImpl { this.toughness = new MageInt(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. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WindingConstrictorPermanentEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WindingConstrictorPermanentEffect())); // If you would get one or more counters, you get that many plus one of each of those kinds of counters instead. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WindingConstrictorPlayerEffect())); @@ -62,7 +57,7 @@ class WindingConstrictorPermanentEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmountForCounters(event.getAmount() + 1, true); return false; } @@ -78,6 +73,7 @@ class WindingConstrictorPermanentEffect extends ReplacementEffectImpl { permanent = game.getPermanentEntering(event.getTargetId()); } return permanent != null + && event.getAmount() > 0 && (permanent.isCreature() || permanent.isArtifact()) && permanent.isControlledBy(source.getControllerId()); } @@ -106,7 +102,7 @@ class WindingConstrictorPlayerEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmountForCounters(event.getAmount() + 1, true); return false; } @@ -118,7 +114,7 @@ class WindingConstrictorPlayerEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { Player player = game.getPlayer(event.getTargetId()); - return player != null && player.getId().equals(source.getControllerId()); + return player != null && player.getId().equals(source.getControllerId()) && event.getAmount() > 0; } @Override diff --git a/Mage.Sets/src/mage/cards/w/WoodlandBellower.java b/Mage.Sets/src/mage/cards/w/WoodlandBellower.java index 9f8ff2a3bc..f7df876681 100644 --- a/Mage.Sets/src/mage/cards/w/WoodlandBellower.java +++ b/Mage.Sets/src/mage/cards/w/WoodlandBellower.java @@ -75,7 +75,7 @@ class WoodlandBellowerEffect extends OneShotEffect { filter.add(Predicates.not(new SupertypePredicate(SuperType.LEGENDARY))); filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, 4)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/cards/w/WormsOfTheEarth.java b/Mage.Sets/src/mage/cards/w/WormsOfTheEarth.java index 35685256a9..128d05dbd8 100644 --- a/Mage.Sets/src/mage/cards/w/WormsOfTheEarth.java +++ b/Mage.Sets/src/mage/cards/w/WormsOfTheEarth.java @@ -1,38 +1,33 @@ - package mage.cards.w; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; 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.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterControlledLandPermanent; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author L_J */ public final class WormsOfTheEarth extends CardImpl { public WormsOfTheEarth(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{B}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{B}{B}"); // Players can't play lands. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WormsOfTheEarthPlayEffect())); @@ -60,7 +55,7 @@ class WormsOfTheEarthPlayEffect extends ContinuousRuleModifyingEffectImpl { super(Duration.WhileOnBattlefield, Outcome.Neutral); this.staticText = "Players can't play lands"; } - + public WormsOfTheEarthPlayEffect(final WormsOfTheEarthPlayEffect effect) { super(effect); } @@ -69,7 +64,7 @@ class WormsOfTheEarthPlayEffect extends ContinuousRuleModifyingEffectImpl { public WormsOfTheEarthPlayEffect copy() { return new WormsOfTheEarthPlayEffect(this); } - + @Override public boolean apply(Game game, Ability source) { return true; @@ -104,7 +99,7 @@ class WormsOfTheEarthEnterEffect extends ContinuousRuleModifyingEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return GameEvent.EventType.ZONE_CHANGE == event.getType(); + return event.getType() == GameEvent.EventType.ZONE_CHANGE; } @Override @@ -112,9 +107,7 @@ class WormsOfTheEarthEnterEffect extends ContinuousRuleModifyingEffectImpl { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (zEvent.getToZone() == Zone.BATTLEFIELD) { Card card = game.getCard(zEvent.getTargetId()); - if (card != null && card.isLand()) { - return true; - } + return card != null && card.isLand(); } return false; } @@ -142,7 +135,7 @@ class WormsOfTheEarthDestroyEffect extends OneShotEffect { if (player != null) { if (player.chooseUse(outcome, "Do you want to destroy " + sourcePermanent.getLogName() + "? (sacrifice two lands or have it deal 5 damage to you)", source, game)) { cost.clearPaid(); - if (cost.canPay(source, source.getSourceId(), player.getId(), game) + if (cost.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Sacrifice, "Will you sacrifice two lands? (otherwise you'll be dealt 5 damage)", source, game)) { if (!cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) { player.damage(5, source.getSourceId(), game, false, true); diff --git a/Mage.Sets/src/mage/cards/y/YavimayaDryad.java b/Mage.Sets/src/mage/cards/y/YavimayaDryad.java index 68944bf154..9890421bc8 100644 --- a/Mage.Sets/src/mage/cards/y/YavimayaDryad.java +++ b/Mage.Sets/src/mage/cards/y/YavimayaDryad.java @@ -80,7 +80,7 @@ class YavimayaDryadEffect extends SearchEffect { if (controller == null || targetPlayer == null) { return false; } - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { targetPlayer.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); diff --git a/Mage.Sets/src/mage/cards/y/YisanTheWandererBard.java b/Mage.Sets/src/mage/cards/y/YisanTheWandererBard.java index f06a2cf576..63cbad9cc5 100644 --- a/Mage.Sets/src/mage/cards/y/YisanTheWandererBard.java +++ b/Mage.Sets/src/mage/cards/y/YisanTheWandererBard.java @@ -85,7 +85,7 @@ class YisanTheWandererBardEffect extends OneShotEffect { filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, newConvertedCost)); filter.add(new CardTypePredicate(CardType.CREATURE)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); controller.moveCards(card, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/z/ZirilanOfTheClaw.java b/Mage.Sets/src/mage/cards/z/ZirilanOfTheClaw.java index 68325e1e24..a014a518c6 100644 --- a/Mage.Sets/src/mage/cards/z/ZirilanOfTheClaw.java +++ b/Mage.Sets/src/mage/cards/z/ZirilanOfTheClaw.java @@ -80,7 +80,7 @@ class ZirilanOfTheClawEffect extends OneShotEffect { FilterPermanentCard filter = new FilterPermanentCard("a Dragon permanent card"); filter.add(new SubtypePredicate(SubType.DRAGON)); TargetCardInLibrary target = new TargetCardInLibrary(filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { controller.moveCards(card, Zone.BATTLEFIELD, source, game); diff --git a/Mage.Sets/src/mage/sets/Alliances.java b/Mage.Sets/src/mage/sets/Alliances.java index c8be8d1dfc..a93161e725 100644 --- a/Mage.Sets/src/mage/sets/Alliances.java +++ b/Mage.Sets/src/mage/sets/Alliances.java @@ -83,6 +83,8 @@ public final class Alliances extends ExpansionSet { cards.add(new SetCardInfo("Fyndhorn Druid", "90a", Rarity.COMMON, mage.cards.f.FyndhornDruid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fyndhorn Druid", "90b", Rarity.COMMON, mage.cards.f.FyndhornDruid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gargantuan Gorilla", 91, Rarity.RARE, mage.cards.g.GargantuanGorilla.class)); + cards.add(new SetCardInfo("Gift of the Woods", "92a", Rarity.COMMON, mage.cards.g.GiftOfTheWoods.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gift of the Woods", "92b", Rarity.COMMON, mage.cards.g.GiftOfTheWoods.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gorilla Berserkers", "93a", Rarity.COMMON, mage.cards.g.GorillaBerserkers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gorilla Berserkers", "93b", Rarity.COMMON, mage.cards.g.GorillaBerserkers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gorilla Chieftain", "94a", Rarity.COMMON, mage.cards.g.GorillaChieftain.class, NON_FULL_USE_VARIOUS)); @@ -106,6 +108,8 @@ public final class Alliances extends ExpansionSet { cards.add(new SetCardInfo("Keeper of Tresserhorn", 52, Rarity.RARE, mage.cards.k.KeeperOfTresserhorn.class)); cards.add(new SetCardInfo("Kjeldoran Escort", "7a", Rarity.COMMON, mage.cards.k.KjeldoranEscort.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kjeldoran Escort", "7b", Rarity.COMMON, mage.cards.k.KjeldoranEscort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kjeldoran Pride", "9a", Rarity.COMMON, mage.cards.k.KjeldoranPride.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kjeldoran Pride", "9b", Rarity.COMMON, mage.cards.k.KjeldoranPride.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kjeldoran Home Guard", 8, Rarity.UNCOMMON, mage.cards.k.KjeldoranHomeGuard.class)); cards.add(new SetCardInfo("Kjeldoran Outpost", 139, Rarity.RARE, mage.cards.k.KjeldoranOutpost.class)); cards.add(new SetCardInfo("Krovikan Horror", 53, Rarity.RARE, mage.cards.k.KrovikanHorror.class)); @@ -149,6 +153,7 @@ public final class Alliances extends ExpansionSet { cards.add(new SetCardInfo("Reprisal", "13a", Rarity.COMMON, mage.cards.r.Reprisal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Reprisal", "13b", Rarity.COMMON, mage.cards.r.Reprisal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ritual of the Machine", 59, Rarity.RARE, mage.cards.r.RitualOfTheMachine.class)); + cards.add(new SetCardInfo("Rogue Skycaptain", 79, Rarity.RARE, mage.cards.r.RogueSkycaptain.class)); cards.add(new SetCardInfo("Royal Decree", 14, Rarity.RARE, mage.cards.r.RoyalDecree.class)); cards.add(new SetCardInfo("Royal Herbalist", "15a", Rarity.COMMON, mage.cards.r.RoyalHerbalist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Royal Herbalist", "15b", Rarity.COMMON, mage.cards.r.RoyalHerbalist.class, NON_FULL_USE_VARIOUS)); @@ -166,6 +171,8 @@ public final class Alliances extends ExpansionSet { cards.add(new SetCardInfo("Soldevi Heretic", "33b", Rarity.COMMON, mage.cards.s.SoldeviHeretic.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soldevi Sage", "34a", Rarity.COMMON, mage.cards.s.SoldeviSage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soldevi Sage", "34b", Rarity.COMMON, mage.cards.s.SoldeviSage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soldevi Sentry", "132a", Rarity.COMMON, mage.cards.s.SoldeviSentry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soldevi Sentry", "132b", Rarity.COMMON, mage.cards.s.SoldeviSentry.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soldevi Steam Beast", "133a", Rarity.COMMON, mage.cards.s.SoldeviSteamBeast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soldevi Steam Beast", "133b", Rarity.COMMON, mage.cards.s.SoldeviSteamBeast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soldier of Fortune", 80, Rarity.UNCOMMON, mage.cards.s.SoldierOfFortune.class)); diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index 863417b6be..5c0ab4bc85 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -1,379 +1,380 @@ -package mage.sets; - -import mage.cards.ExpansionSet; -import mage.constants.Rarity; -import mage.constants.SetType; - -/** - * @author North - */ -public final class IceAge extends ExpansionSet { - - private static final IceAge instance = new IceAge(); - - public static IceAge getInstance() { - return instance; - } - - private IceAge() { - super("Ice Age", "ICE", ExpansionSet.buildDate(1995, 5, 1), SetType.EXPANSION); - this.blockName = "Ice Age"; - this.hasBoosters = true; - this.numBoosterLands = 0; - this.numBoosterCommon = 11; - this.numBoosterUncommon = 3; - this.numBoosterRare = 1; - this.ratioBoosterMythic = 0; - - cards.add(new SetCardInfo("Abyssal Specter", 113, Rarity.UNCOMMON, mage.cards.a.AbyssalSpecter.class)); - cards.add(new SetCardInfo("Adarkar Sentinel", 306, Rarity.UNCOMMON, mage.cards.a.AdarkarSentinel.class)); - cards.add(new SetCardInfo("Adarkar Wastes", 351, Rarity.RARE, mage.cards.a.AdarkarWastes.class)); - cards.add(new SetCardInfo("Aegis of the Meek", 307, Rarity.RARE, mage.cards.a.AegisOfTheMeek.class)); - cards.add(new SetCardInfo("Aggression", 169, Rarity.UNCOMMON, mage.cards.a.Aggression.class)); - 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("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)); - cards.add(new SetCardInfo("Ashen Ghoul", 114, Rarity.UNCOMMON, mage.cards.a.AshenGhoul.class)); - cards.add(new SetCardInfo("Aurochs", 225, Rarity.COMMON, mage.cards.a.Aurochs.class)); - cards.add(new SetCardInfo("Avalanche", 171, Rarity.UNCOMMON, mage.cards.a.Avalanche.class)); - cards.add(new SetCardInfo("Balduvian Barbarians", 172, Rarity.COMMON, mage.cards.b.BalduvianBarbarians.class)); - cards.add(new SetCardInfo("Balduvian Bears", 226, Rarity.COMMON, mage.cards.b.BalduvianBears.class)); - cards.add(new SetCardInfo("Balduvian Conjurer", 58, Rarity.UNCOMMON, mage.cards.b.BalduvianConjurer.class)); - cards.add(new SetCardInfo("Balduvian Hydra", 173, Rarity.RARE, mage.cards.b.BalduvianHydra.class)); - cards.add(new SetCardInfo("Barbed Sextant", 312, Rarity.COMMON, mage.cards.b.BarbedSextant.class)); - cards.add(new SetCardInfo("Baton of Morale", 313, Rarity.UNCOMMON, mage.cards.b.BatonOfMorale.class)); - cards.add(new SetCardInfo("Battle Cry", 5, Rarity.UNCOMMON, mage.cards.b.BattleCry.class)); - cards.add(new SetCardInfo("Battle Frenzy", 175, Rarity.COMMON, mage.cards.b.BattleFrenzy.class)); - cards.add(new SetCardInfo("Binding Grasp", 60, Rarity.UNCOMMON, mage.cards.b.BindingGrasp.class)); - cards.add(new SetCardInfo("Black Scarab", 6, Rarity.UNCOMMON, mage.cards.b.BlackScarab.class)); - cards.add(new SetCardInfo("Blessed Wine", 7, Rarity.COMMON, mage.cards.b.BlessedWine.class)); - cards.add(new SetCardInfo("Blinking Spirit", 8, Rarity.RARE, mage.cards.b.BlinkingSpirit.class)); - cards.add(new SetCardInfo("Blizzard", 227, Rarity.RARE, mage.cards.b.Blizzard.class)); - cards.add(new SetCardInfo("Blue Scarab", 9, Rarity.UNCOMMON, mage.cards.b.BlueScarab.class)); - cards.add(new SetCardInfo("Brainstorm", 61, Rarity.COMMON, mage.cards.b.Brainstorm.class)); - cards.add(new SetCardInfo("Brand of Ill Omen", 177, Rarity.RARE, mage.cards.b.BrandOfIllOmen.class)); - cards.add(new SetCardInfo("Breath of Dreams", 62, Rarity.UNCOMMON, mage.cards.b.BreathOfDreams.class)); - cards.add(new SetCardInfo("Brine Shaman", 115, Rarity.COMMON, mage.cards.b.BrineShaman.class)); - cards.add(new SetCardInfo("Brown Ouphe", 228, Rarity.COMMON, mage.cards.b.BrownOuphe.class)); - cards.add(new SetCardInfo("Brushland", 352, Rarity.RARE, mage.cards.b.Brushland.class)); - cards.add(new SetCardInfo("Burnt Offering", 116, Rarity.COMMON, mage.cards.b.BurntOffering.class)); - cards.add(new SetCardInfo("Call to Arms", 10, Rarity.RARE, mage.cards.c.CallToArms.class)); - cards.add(new SetCardInfo("Caribou Range", 11, Rarity.RARE, mage.cards.c.CaribouRange.class)); - cards.add(new SetCardInfo("Celestial Sword", 314, Rarity.RARE, mage.cards.c.CelestialSword.class)); - cards.add(new SetCardInfo("Centaur Archer", 282, Rarity.UNCOMMON, mage.cards.c.CentaurArcher.class)); - cards.add(new SetCardInfo("Chaos Lord", 178, Rarity.RARE, mage.cards.c.ChaosLord.class)); - cards.add(new SetCardInfo("Chaos Moon", 179, Rarity.RARE, mage.cards.c.ChaosMoon.class)); - cards.add(new SetCardInfo("Chub Toad", 229, Rarity.COMMON, mage.cards.c.ChubToad.class)); - cards.add(new SetCardInfo("Circle of Protection: Black", 12, Rarity.COMMON, mage.cards.c.CircleOfProtectionBlack.class)); - cards.add(new SetCardInfo("Circle of Protection: Blue", 13, Rarity.COMMON, mage.cards.c.CircleOfProtectionBlue.class)); - cards.add(new SetCardInfo("Circle of Protection: Green", 14, Rarity.COMMON, mage.cards.c.CircleOfProtectionGreen.class)); - cards.add(new SetCardInfo("Circle of Protection: Red", 15, Rarity.COMMON, mage.cards.c.CircleOfProtectionRed.class)); - cards.add(new SetCardInfo("Circle of Protection: White", 16, Rarity.COMMON, mage.cards.c.CircleOfProtectionWhite.class)); - cards.add(new SetCardInfo("Clairvoyance", 63, Rarity.COMMON, mage.cards.c.Clairvoyance.class)); - cards.add(new SetCardInfo("Cloak of Confusion", 117, Rarity.COMMON, mage.cards.c.CloakOfConfusion.class)); - cards.add(new SetCardInfo("Cold Snap", 17, Rarity.UNCOMMON, mage.cards.c.ColdSnap.class)); - cards.add(new SetCardInfo("Conquer", 180, Rarity.UNCOMMON, mage.cards.c.Conquer.class)); - cards.add(new SetCardInfo("Cooperation", 18, Rarity.COMMON, mage.cards.c.Cooperation.class)); - cards.add(new SetCardInfo("Counterspell", 64, Rarity.COMMON, mage.cards.c.Counterspell.class)); - cards.add(new SetCardInfo("Crown of the Ages", 315, Rarity.RARE, mage.cards.c.CrownOfTheAges.class)); - cards.add(new SetCardInfo("Curse of Marit Lage", 181, Rarity.RARE, mage.cards.c.CurseOfMaritLage.class)); - cards.add(new SetCardInfo("Dance of the Dead", 118, Rarity.UNCOMMON, mage.cards.d.DanceOfTheDead.class)); - cards.add(new SetCardInfo("Dark Banishing", 119, Rarity.COMMON, mage.cards.d.DarkBanishing.class)); - cards.add(new SetCardInfo("Dark Ritual", 120, Rarity.COMMON, mage.cards.d.DarkRitual.class)); - cards.add(new SetCardInfo("Death Ward", 19, Rarity.COMMON, mage.cards.d.DeathWard.class)); - cards.add(new SetCardInfo("Deflection", 65, Rarity.RARE, mage.cards.d.Deflection.class)); - cards.add(new SetCardInfo("Demonic Consultation", 121, Rarity.UNCOMMON, mage.cards.d.DemonicConsultation.class)); - cards.add(new SetCardInfo("Despotic Scepter", 316, Rarity.RARE, mage.cards.d.DespoticScepter.class)); - cards.add(new SetCardInfo("Diabolic Vision", 284, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class)); - cards.add(new SetCardInfo("Dire Wolves", 230, Rarity.COMMON, mage.cards.d.DireWolves.class)); - cards.add(new SetCardInfo("Disenchant", 20, Rarity.COMMON, mage.cards.d.Disenchant.class)); - cards.add(new SetCardInfo("Dread Wight", 122, Rarity.RARE, mage.cards.d.DreadWight.class)); - cards.add(new SetCardInfo("Dreams of the Dead", 66, Rarity.UNCOMMON, mage.cards.d.DreamsOfTheDead.class)); - cards.add(new SetCardInfo("Drift of the Dead", 123, Rarity.UNCOMMON, mage.cards.d.DriftOfTheDead.class)); - cards.add(new SetCardInfo("Drought", 21, Rarity.UNCOMMON, mage.cards.d.Drought.class)); - cards.add(new SetCardInfo("Dwarven Armory", 182, Rarity.RARE, mage.cards.d.DwarvenArmory.class)); - cards.add(new SetCardInfo("Earthlink", 285, Rarity.RARE, mage.cards.e.Earthlink.class)); - cards.add(new SetCardInfo("Earthlore", 231, Rarity.COMMON, mage.cards.e.Earthlore.class)); - cards.add(new SetCardInfo("Elder Druid", 232, Rarity.RARE, mage.cards.e.ElderDruid.class)); - cards.add(new SetCardInfo("Elemental Augury", 286, Rarity.RARE, mage.cards.e.ElementalAugury.class)); - cards.add(new SetCardInfo("Elkin Bottle", 317, Rarity.RARE, mage.cards.e.ElkinBottle.class)); - cards.add(new SetCardInfo("Enduring Renewal", 23, Rarity.RARE, mage.cards.e.EnduringRenewal.class)); - cards.add(new SetCardInfo("Energy Storm", 24, Rarity.RARE, mage.cards.e.EnergyStorm.class)); - cards.add(new SetCardInfo("Enervate", 67, Rarity.COMMON, mage.cards.e.Enervate.class)); - cards.add(new SetCardInfo("Errant Minion", 68, Rarity.COMMON, mage.cards.e.ErrantMinion.class)); - cards.add(new SetCardInfo("Errantry", 183, Rarity.COMMON, mage.cards.e.Errantry.class)); - cards.add(new SetCardInfo("Essence Filter", 233, Rarity.COMMON, mage.cards.e.EssenceFilter.class)); - cards.add(new SetCardInfo("Essence Flare", 69, Rarity.COMMON, mage.cards.e.EssenceFlare.class)); - cards.add(new SetCardInfo("Fanatical Fever", 234, Rarity.UNCOMMON, mage.cards.f.FanaticalFever.class)); - cards.add(new SetCardInfo("Fear", 124, Rarity.COMMON, mage.cards.f.Fear.class)); - cards.add(new SetCardInfo("Fiery Justice", 288, Rarity.RARE, mage.cards.f.FieryJustice.class)); - cards.add(new SetCardInfo("Fire Covenant", 289, Rarity.UNCOMMON, mage.cards.f.FireCovenant.class)); - cards.add(new SetCardInfo("Flame Spirit", 184, Rarity.UNCOMMON, mage.cards.f.FlameSpirit.class)); - cards.add(new SetCardInfo("Flare", 185, Rarity.COMMON, mage.cards.f.Flare.class)); - cards.add(new SetCardInfo("Flooded Woodlands", 290, Rarity.RARE, mage.cards.f.FloodedWoodlands.class)); - cards.add(new SetCardInfo("Flow of Maggots", 125, Rarity.RARE, mage.cards.f.FlowOfMaggots.class)); - cards.add(new SetCardInfo("Folk of the Pines", 235, Rarity.COMMON, mage.cards.f.FolkOfThePines.class)); - cards.add(new SetCardInfo("Forbidden Lore", 236, Rarity.RARE, mage.cards.f.ForbiddenLore.class)); - cards.add(new SetCardInfo("Force Void", 70, Rarity.UNCOMMON, mage.cards.f.ForceVoid.class)); - cards.add(new SetCardInfo("Forest", 380, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 381, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 382, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forgotten Lore", 237, Rarity.UNCOMMON, mage.cards.f.ForgottenLore.class)); - cards.add(new SetCardInfo("Formation", 25, Rarity.RARE, mage.cards.f.Formation.class)); - cards.add(new SetCardInfo("Foul Familiar", 126, Rarity.COMMON, mage.cards.f.FoulFamiliar.class)); - cards.add(new SetCardInfo("Foxfire", 238, Rarity.COMMON, mage.cards.f.Foxfire.class)); - cards.add(new SetCardInfo("Freyalise Supplicant", 239, Rarity.UNCOMMON, mage.cards.f.FreyaliseSupplicant.class)); - cards.add(new SetCardInfo("Freyalise's Charm", 240, Rarity.UNCOMMON, mage.cards.f.FreyalisesCharm.class)); - cards.add(new SetCardInfo("Freyalise's Winds", 241, Rarity.RARE, mage.cards.f.FreyalisesWinds.class)); - cards.add(new SetCardInfo("Fumarole", 291, Rarity.UNCOMMON, mage.cards.f.Fumarole.class)); - cards.add(new SetCardInfo("Fyndhorn Bow", 318, Rarity.UNCOMMON, mage.cards.f.FyndhornBow.class)); - cards.add(new SetCardInfo("Fyndhorn Brownie", 242, Rarity.COMMON, mage.cards.f.FyndhornBrownie.class)); - cards.add(new SetCardInfo("Fyndhorn Elder", 243, Rarity.UNCOMMON, mage.cards.f.FyndhornElder.class)); - cards.add(new SetCardInfo("Fyndhorn Elves", 244, Rarity.COMMON, mage.cards.f.FyndhornElves.class)); - cards.add(new SetCardInfo("Fyndhorn Pollen", 245, Rarity.RARE, mage.cards.f.FyndhornPollen.class)); - cards.add(new SetCardInfo("Game of Chaos", 186, Rarity.RARE, mage.cards.g.GameOfChaos.class)); - cards.add(new SetCardInfo("Gangrenous Zombies", 127, Rarity.COMMON, mage.cards.g.GangrenousZombies.class)); - cards.add(new SetCardInfo("Gaze of Pain", 128, Rarity.COMMON, mage.cards.g.GazeOfPain.class)); - cards.add(new SetCardInfo("General Jarkeld", 27, Rarity.RARE, mage.cards.g.GeneralJarkeld.class)); - cards.add(new SetCardInfo("Giant Growth", 246, Rarity.COMMON, mage.cards.g.GiantGrowth.class)); - cards.add(new SetCardInfo("Giant Trap Door Spider", 293, Rarity.UNCOMMON, mage.cards.g.GiantTrapDoorSpider.class)); - cards.add(new SetCardInfo("Glacial Chasm", 353, Rarity.UNCOMMON, mage.cards.g.GlacialChasm.class)); - cards.add(new SetCardInfo("Glacial Crevasses", 187, Rarity.RARE, mage.cards.g.GlacialCrevasses.class)); - cards.add(new SetCardInfo("Glacial Wall", 71, Rarity.UNCOMMON, mage.cards.g.GlacialWall.class)); - cards.add(new SetCardInfo("Glaciers", 294, Rarity.RARE, mage.cards.g.Glaciers.class)); - cards.add(new SetCardInfo("Goblin Lyre", 319, Rarity.RARE, mage.cards.g.GoblinLyre.class)); - cards.add(new SetCardInfo("Goblin Mutant", 188, Rarity.UNCOMMON, mage.cards.g.GoblinMutant.class)); - cards.add(new SetCardInfo("Goblin Snowman", 191, Rarity.UNCOMMON, mage.cards.g.GoblinSnowman.class)); - cards.add(new SetCardInfo("Gorilla Pack", 247, Rarity.COMMON, mage.cards.g.GorillaPack.class)); - cards.add(new SetCardInfo("Gravebind", 129, Rarity.RARE, mage.cards.g.Gravebind.class)); - cards.add(new SetCardInfo("Green Scarab", 28, Rarity.UNCOMMON, mage.cards.g.GreenScarab.class)); - cards.add(new SetCardInfo("Hallowed Ground", 29, Rarity.UNCOMMON, mage.cards.h.HallowedGround.class)); - cards.add(new SetCardInfo("Halls of Mist", 354, Rarity.RARE, mage.cards.h.HallsOfMist.class)); - cards.add(new SetCardInfo("Heal", 30, Rarity.COMMON, mage.cards.h.Heal.class)); - cards.add(new SetCardInfo("Hecatomb", 130, Rarity.RARE, mage.cards.h.Hecatomb.class)); - cards.add(new SetCardInfo("Hematite Talisman", 320, Rarity.UNCOMMON, mage.cards.h.HematiteTalisman.class)); - cards.add(new SetCardInfo("Hoar Shade", 131, Rarity.COMMON, mage.cards.h.HoarShade.class)); - cards.add(new SetCardInfo("Hot Springs", 248, Rarity.RARE, mage.cards.h.HotSprings.class)); - cards.add(new SetCardInfo("Howl from Beyond", 132, Rarity.COMMON, mage.cards.h.HowlFromBeyond.class)); - cards.add(new SetCardInfo("Hurricane", 249, Rarity.UNCOMMON, mage.cards.h.Hurricane.class)); - cards.add(new SetCardInfo("Hyalopterous Lemure", 133, Rarity.UNCOMMON, mage.cards.h.HyalopterousLemure.class)); - cards.add(new SetCardInfo("Hydroblast", 72, Rarity.COMMON, mage.cards.h.Hydroblast.class)); - cards.add(new SetCardInfo("Hymn of Rebirth", 295, Rarity.UNCOMMON, mage.cards.h.HymnOfRebirth.class)); - cards.add(new SetCardInfo("Ice Cauldron", 321, Rarity.RARE, mage.cards.i.IceCauldron.class)); - cards.add(new SetCardInfo("Ice Floe", 355, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); - cards.add(new SetCardInfo("Iceberg", 73, Rarity.UNCOMMON, mage.cards.i.Iceberg.class)); - cards.add(new SetCardInfo("Icequake", 134, Rarity.UNCOMMON, mage.cards.i.Icequake.class)); - cards.add(new SetCardInfo("Icy Manipulator", 322, Rarity.UNCOMMON, mage.cards.i.IcyManipulator.class)); - cards.add(new SetCardInfo("Icy Prison", 74, Rarity.RARE, mage.cards.i.IcyPrison.class)); - cards.add(new SetCardInfo("Illusionary Forces", 75, Rarity.COMMON, mage.cards.i.IllusionaryForces.class)); - cards.add(new SetCardInfo("Illusionary Presence", 76, Rarity.RARE, mage.cards.i.IllusionaryPresence.class)); - cards.add(new SetCardInfo("Illusionary Terrain", 77, Rarity.UNCOMMON, mage.cards.i.IllusionaryTerrain.class)); - cards.add(new SetCardInfo("Illusionary Wall", 78, Rarity.COMMON, mage.cards.i.IllusionaryWall.class)); - cards.add(new SetCardInfo("Illusions of Grandeur", 79, Rarity.RARE, mage.cards.i.IllusionsOfGrandeur.class)); - cards.add(new SetCardInfo("Imposing Visage", 193, Rarity.COMMON, mage.cards.i.ImposingVisage.class)); - cards.add(new SetCardInfo("Incinerate", 194, Rarity.COMMON, mage.cards.i.Incinerate.class)); - cards.add(new SetCardInfo("Infernal Darkness", 135, Rarity.RARE, mage.cards.i.InfernalDarkness.class)); - cards.add(new SetCardInfo("Infernal Denizen", 136, Rarity.RARE, mage.cards.i.InfernalDenizen.class)); - cards.add(new SetCardInfo("Infinite Hourglass", 323, Rarity.RARE, mage.cards.i.InfiniteHourglass.class)); - cards.add(new SetCardInfo("Infuse", 80, Rarity.COMMON, mage.cards.i.Infuse.class)); - cards.add(new SetCardInfo("Island", 368, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 369, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 370, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Jester's Cap", 324, Rarity.RARE, mage.cards.j.JestersCap.class)); - cards.add(new SetCardInfo("Jester's Mask", 325, Rarity.RARE, mage.cards.j.JestersMask.class)); - cards.add(new SetCardInfo("Jeweled Amulet", 326, Rarity.UNCOMMON, mage.cards.j.JeweledAmulet.class)); - cards.add(new SetCardInfo("Johtull Wurm", 250, Rarity.UNCOMMON, mage.cards.j.JohtullWurm.class)); - cards.add(new SetCardInfo("Jokulhaups", 195, Rarity.RARE, mage.cards.j.Jokulhaups.class)); - cards.add(new SetCardInfo("Juniper Order Druid", 251, Rarity.COMMON, mage.cards.j.JuniperOrderDruid.class)); - cards.add(new SetCardInfo("Justice", 32, Rarity.UNCOMMON, mage.cards.j.Justice.class)); - cards.add(new SetCardInfo("Karplusan Forest", 356, Rarity.RARE, mage.cards.k.KarplusanForest.class)); - cards.add(new SetCardInfo("Karplusan Giant", 196, Rarity.UNCOMMON, mage.cards.k.KarplusanGiant.class)); - cards.add(new SetCardInfo("Karplusan Yeti", 197, Rarity.RARE, mage.cards.k.KarplusanYeti.class)); - cards.add(new SetCardInfo("Kelsinko Ranger", 33, Rarity.COMMON, mage.cards.k.KelsinkoRanger.class)); - cards.add(new SetCardInfo("Kjeldoran Dead", 137, Rarity.COMMON, mage.cards.k.KjeldoranDead.class)); - cards.add(new SetCardInfo("Kjeldoran Frostbeast", 296, Rarity.UNCOMMON, mage.cards.k.KjeldoranFrostbeast.class)); - cards.add(new SetCardInfo("Kjeldoran Knight", 36, Rarity.RARE, mage.cards.k.KjeldoranKnight.class)); - cards.add(new SetCardInfo("Kjeldoran Phalanx", 37, Rarity.RARE, mage.cards.k.KjeldoranPhalanx.class)); - cards.add(new SetCardInfo("Kjeldoran Royal Guard", 38, Rarity.RARE, mage.cards.k.KjeldoranRoyalGuard.class)); - cards.add(new SetCardInfo("Kjeldoran Skycaptain", 39, Rarity.UNCOMMON, mage.cards.k.KjeldoranSkycaptain.class)); - cards.add(new SetCardInfo("Kjeldoran Skyknight", 40, Rarity.COMMON, mage.cards.k.KjeldoranSkyknight.class)); - cards.add(new SetCardInfo("Kjeldoran Warrior", 41, Rarity.COMMON, mage.cards.k.KjeldoranWarrior.class)); - cards.add(new SetCardInfo("Knight of Stromgald", 138, Rarity.UNCOMMON, mage.cards.k.KnightOfStromgald.class)); - cards.add(new SetCardInfo("Krovikan Elementalist", 139, Rarity.UNCOMMON, mage.cards.k.KrovikanElementalist.class)); - cards.add(new SetCardInfo("Krovikan Fetish", 140, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); - cards.add(new SetCardInfo("Krovikan Sorcerer", 81, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); - cards.add(new SetCardInfo("Krovikan Vampire", 141, Rarity.UNCOMMON, mage.cards.k.KrovikanVampire.class)); - cards.add(new SetCardInfo("Land Cap", 357, Rarity.RARE, mage.cards.l.LandCap.class)); - cards.add(new SetCardInfo("Lapis Lazuli Talisman", 327, Rarity.UNCOMMON, mage.cards.l.LapisLazuliTalisman.class)); - cards.add(new SetCardInfo("Lava Tubes", 358, Rarity.RARE, mage.cards.l.LavaTubes.class)); - cards.add(new SetCardInfo("Legions of Lim-Dul", 142, Rarity.COMMON, mage.cards.l.LegionsOfLimDul.class)); - cards.add(new SetCardInfo("Leshrac's Rite", 143, Rarity.UNCOMMON, mage.cards.l.LeshracsRite.class)); - cards.add(new SetCardInfo("Leshrac's Sigil", 144, Rarity.UNCOMMON, mage.cards.l.LeshracsSigil.class)); - cards.add(new SetCardInfo("Lhurgoyf", 252, Rarity.RARE, mage.cards.l.Lhurgoyf.class)); - cards.add(new SetCardInfo("Lightning Blow", 42, Rarity.RARE, mage.cards.l.LightningBlow.class)); - cards.add(new SetCardInfo("Lim-Dul's Cohort", 145, Rarity.COMMON, mage.cards.l.LimDulsCohort.class)); - cards.add(new SetCardInfo("Lim-Dul's Hex", 146, Rarity.UNCOMMON, mage.cards.l.LimDulsHex.class)); - cards.add(new SetCardInfo("Lost Order of Jarkeld", 43, Rarity.RARE, mage.cards.l.LostOrderOfJarkeld.class)); - cards.add(new SetCardInfo("Lure", 253, Rarity.UNCOMMON, mage.cards.l.Lure.class)); - cards.add(new SetCardInfo("Magus of the Unseen", 82, Rarity.RARE, mage.cards.m.MagusOfTheUnseen.class)); - cards.add(new SetCardInfo("Malachite Talisman", 328, Rarity.UNCOMMON, mage.cards.m.MalachiteTalisman.class)); - cards.add(new SetCardInfo("Marton Stromgald", 199, Rarity.RARE, mage.cards.m.MartonStromgald.class)); - cards.add(new SetCardInfo("Melee", 200, Rarity.UNCOMMON, mage.cards.m.Melee.class)); - cards.add(new SetCardInfo("Melting", 201, Rarity.UNCOMMON, mage.cards.m.Melting.class)); - cards.add(new SetCardInfo("Merieke Ri Berit", 297, Rarity.RARE, mage.cards.m.MeriekeRiBerit.class)); - cards.add(new SetCardInfo("Mesmeric Trance", 83, Rarity.RARE, mage.cards.m.MesmericTrance.class)); - cards.add(new SetCardInfo("Meteor Shower", 202, Rarity.COMMON, mage.cards.m.MeteorShower.class)); - cards.add(new SetCardInfo("Mind Ravel", 147, Rarity.COMMON, mage.cards.m.MindRavel.class)); - cards.add(new SetCardInfo("Mind Warp", 148, Rarity.UNCOMMON, mage.cards.m.MindWarp.class)); - cards.add(new SetCardInfo("Mind Whip", 149, Rarity.RARE, mage.cards.m.MindWhip.class)); - cards.add(new SetCardInfo("Minion of Leshrac", 150, Rarity.RARE, mage.cards.m.MinionOfLeshrac.class)); - cards.add(new SetCardInfo("Minion of Tevesh Szat", 151, Rarity.RARE, mage.cards.m.MinionOfTeveshSzat.class)); - cards.add(new SetCardInfo("Mistfolk", 84, Rarity.COMMON, mage.cards.m.Mistfolk.class)); - cards.add(new SetCardInfo("Mole Worms", 152, Rarity.UNCOMMON, mage.cards.m.MoleWorms.class)); - cards.add(new SetCardInfo("Monsoon", 298, Rarity.RARE, mage.cards.m.Monsoon.class)); - cards.add(new SetCardInfo("Moor Fiend", 153, Rarity.COMMON, mage.cards.m.MoorFiend.class)); - cards.add(new SetCardInfo("Mountain Goat", 203, Rarity.COMMON, mage.cards.m.MountainGoat.class)); - cards.add(new SetCardInfo("Mountain Titan", 299, Rarity.RARE, mage.cards.m.MountainTitan.class)); - cards.add(new SetCardInfo("Mountain", 376, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 377, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 378, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mudslide", 204, Rarity.RARE, mage.cards.m.Mudslide.class)); - cards.add(new SetCardInfo("Musician", 85, Rarity.RARE, mage.cards.m.Musician.class)); - cards.add(new SetCardInfo("Mystic Might", 86, Rarity.RARE, mage.cards.m.MysticMight.class)); - cards.add(new SetCardInfo("Mystic Remora", 87, Rarity.COMMON, mage.cards.m.MysticRemora.class)); - cards.add(new SetCardInfo("Nacre Talisman", 329, Rarity.UNCOMMON, mage.cards.n.NacreTalisman.class)); - cards.add(new SetCardInfo("Naked Singularity", 330, Rarity.RARE, mage.cards.n.NakedSingularity.class)); - cards.add(new SetCardInfo("Nature's Lore", 255, Rarity.UNCOMMON, mage.cards.n.NaturesLore.class)); - cards.add(new SetCardInfo("Necropotence", 154, Rarity.RARE, mage.cards.n.Necropotence.class)); - cards.add(new SetCardInfo("Norritt", 155, Rarity.COMMON, mage.cards.n.Norritt.class)); - cards.add(new SetCardInfo("Oath of Lim-Dul", 156, Rarity.RARE, mage.cards.o.OathOfLimDul.class)); - cards.add(new SetCardInfo("Onyx Talisman", 331, Rarity.UNCOMMON, mage.cards.o.OnyxTalisman.class)); - cards.add(new SetCardInfo("Orcish Cannoneers", 205, Rarity.UNCOMMON, mage.cards.o.OrcishCannoneers.class)); - cards.add(new SetCardInfo("Orcish Healer", 208, Rarity.UNCOMMON, mage.cards.o.OrcishHealer.class)); - cards.add(new SetCardInfo("Orcish Librarian", 209, Rarity.RARE, mage.cards.o.OrcishLibrarian.class)); - cards.add(new SetCardInfo("Orcish Lumberjack", 210, Rarity.COMMON, mage.cards.o.OrcishLumberjack.class)); - cards.add(new SetCardInfo("Orcish Squatters", 211, Rarity.RARE, mage.cards.o.OrcishSquatters.class)); - cards.add(new SetCardInfo("Order of the Sacred Torch", 45, Rarity.RARE, mage.cards.o.OrderOfTheSacredTorch.class)); - cards.add(new SetCardInfo("Order of the White Shield", 46, Rarity.UNCOMMON, mage.cards.o.OrderOfTheWhiteShield.class)); - cards.add(new SetCardInfo("Pale Bears", 256, Rarity.RARE, mage.cards.p.PaleBears.class)); - cards.add(new SetCardInfo("Panic", 212, Rarity.COMMON, mage.cards.p.Panic.class)); - cards.add(new SetCardInfo("Pentagram of the Ages", 332, Rarity.RARE, mage.cards.p.PentagramOfTheAges.class)); - cards.add(new SetCardInfo("Pestilence Rats", 157, Rarity.COMMON, mage.cards.p.PestilenceRats.class)); - cards.add(new SetCardInfo("Phantasmal Mount", 88, Rarity.UNCOMMON, mage.cards.p.PhantasmalMount.class)); - cards.add(new SetCardInfo("Pit Trap", 333, Rarity.UNCOMMON, mage.cards.p.PitTrap.class)); - cards.add(new SetCardInfo("Plains", 364, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 365, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 366, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Polar Kraken", 89, Rarity.RARE, mage.cards.p.PolarKraken.class)); - cards.add(new SetCardInfo("Portent", 90, Rarity.COMMON, mage.cards.p.Portent.class)); - cards.add(new SetCardInfo("Power Sink", 91, Rarity.COMMON, mage.cards.p.PowerSink.class)); - cards.add(new SetCardInfo("Pox", 158, Rarity.RARE, mage.cards.p.Pox.class)); - cards.add(new SetCardInfo("Prismatic Ward", 47, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); - cards.add(new SetCardInfo("Pygmy Allosaurus", 257, Rarity.RARE, mage.cards.p.PygmyAllosaurus.class)); - cards.add(new SetCardInfo("Pyknite", 258, Rarity.COMMON, mage.cards.p.Pyknite.class)); - cards.add(new SetCardInfo("Pyroblast", 213, Rarity.COMMON, mage.cards.p.Pyroblast.class)); - cards.add(new SetCardInfo("Pyroclasm", 214, Rarity.UNCOMMON, mage.cards.p.Pyroclasm.class)); - cards.add(new SetCardInfo("Rally", 48, Rarity.COMMON, mage.cards.r.Rally.class)); - cards.add(new SetCardInfo("Ray of Command", 92, Rarity.COMMON, mage.cards.r.RayOfCommand.class)); - cards.add(new SetCardInfo("Ray of Erasure", 93, Rarity.COMMON, mage.cards.r.RayOfErasure.class)); - cards.add(new SetCardInfo("Reality Twist", 94, Rarity.RARE, mage.cards.r.RealityTwist.class)); - cards.add(new SetCardInfo("Reclamation", 300, Rarity.RARE, mage.cards.r.Reclamation.class)); - cards.add(new SetCardInfo("Red Scarab", 49, Rarity.UNCOMMON, mage.cards.r.RedScarab.class)); - cards.add(new SetCardInfo("Regeneration", 259, Rarity.COMMON, mage.cards.r.Regeneration.class)); - cards.add(new SetCardInfo("Rime Dryad", 260, Rarity.COMMON, mage.cards.r.RimeDryad.class)); - cards.add(new SetCardInfo("Ritual of Subdual", 261, Rarity.RARE, mage.cards.r.RitualOfSubdual.class)); - cards.add(new SetCardInfo("River Delta", 359, Rarity.RARE, mage.cards.r.RiverDelta.class)); - cards.add(new SetCardInfo("Runed Arch", 334, Rarity.RARE, mage.cards.r.RunedArch.class)); - cards.add(new SetCardInfo("Sabretooth Tiger", 215, Rarity.COMMON, mage.cards.s.SabretoothTiger.class)); - cards.add(new SetCardInfo("Scaled Wurm", 262, Rarity.COMMON, mage.cards.s.ScaledWurm.class)); - cards.add(new SetCardInfo("Sea Spirit", 95, Rarity.UNCOMMON, mage.cards.s.SeaSpirit.class)); - cards.add(new SetCardInfo("Seizures", 159, Rarity.COMMON, mage.cards.s.Seizures.class)); - cards.add(new SetCardInfo("Seraph", 51, Rarity.RARE, mage.cards.s.Seraph.class)); - cards.add(new SetCardInfo("Shambling Strider", 263, Rarity.COMMON, mage.cards.s.ShamblingStrider.class)); - cards.add(new SetCardInfo("Shatter", 216, Rarity.COMMON, mage.cards.s.Shatter.class)); - cards.add(new SetCardInfo("Shield Bearer", 52, Rarity.COMMON, mage.cards.s.ShieldBearer.class)); - cards.add(new SetCardInfo("Shield of the Ages", 335, Rarity.UNCOMMON, mage.cards.s.ShieldOfTheAges.class)); - cards.add(new SetCardInfo("Shyft", 96, Rarity.RARE, mage.cards.s.Shyft.class)); - cards.add(new SetCardInfo("Sibilant Spirit", 97, Rarity.RARE, mage.cards.s.SibilantSpirit.class)); - cards.add(new SetCardInfo("Silver Erne", 98, Rarity.UNCOMMON, mage.cards.s.SilverErne.class)); - cards.add(new SetCardInfo("Skeleton Ship", 301, Rarity.RARE, mage.cards.s.SkeletonShip.class)); - cards.add(new SetCardInfo("Skull Catapult", 336, Rarity.UNCOMMON, mage.cards.s.SkullCatapult.class)); - cards.add(new SetCardInfo("Snow Devil", 100, Rarity.COMMON, mage.cards.s.SnowDevil.class)); - cards.add(new SetCardInfo("Snow Fortress", 337, Rarity.RARE, mage.cards.s.SnowFortress.class)); - cards.add(new SetCardInfo("Snow Hound", 53, Rarity.UNCOMMON, mage.cards.s.SnowHound.class)); - cards.add(new SetCardInfo("Snow-Covered Forest", 383, Rarity.LAND, mage.cards.s.SnowCoveredForest.class)); - cards.add(new SetCardInfo("Snow-Covered Island", 371, Rarity.LAND, mage.cards.s.SnowCoveredIsland.class)); - cards.add(new SetCardInfo("Snow-Covered Mountain", 379, Rarity.LAND, mage.cards.s.SnowCoveredMountain.class)); - cards.add(new SetCardInfo("Snow-Covered Plains", 367, Rarity.LAND, mage.cards.s.SnowCoveredPlains.class)); - cards.add(new SetCardInfo("Snow-Covered Swamp", 372, Rarity.LAND, mage.cards.s.SnowCoveredSwamp.class)); - cards.add(new SetCardInfo("Soldevi Golem", 338, Rarity.RARE, mage.cards.s.SoldeviGolem.class)); - cards.add(new SetCardInfo("Soldevi Machinist", 102, Rarity.UNCOMMON, mage.cards.s.SoldeviMachinist.class)); - cards.add(new SetCardInfo("Soldevi Simulacrum", 339, Rarity.UNCOMMON, mage.cards.s.SoldeviSimulacrum.class)); - cards.add(new SetCardInfo("Songs of the Damned", 160, Rarity.COMMON, mage.cards.s.SongsOfTheDamned.class)); - cards.add(new SetCardInfo("Soul Barrier", 103, Rarity.UNCOMMON, mage.cards.s.SoulBarrier.class)); - cards.add(new SetCardInfo("Soul Burn", 161, Rarity.COMMON, mage.cards.s.SoulBurn.class)); - cards.add(new SetCardInfo("Soul Kiss", 162, Rarity.COMMON, mage.cards.s.SoulKiss.class)); - cards.add(new SetCardInfo("Spoils of Evil", 163, Rarity.RARE, mage.cards.s.SpoilsOfEvil.class)); - cards.add(new SetCardInfo("Staff of the Ages", 340, Rarity.RARE, mage.cards.s.StaffOfTheAges.class)); - cards.add(new SetCardInfo("Stampede", 265, Rarity.RARE, mage.cards.s.Stampede.class)); - cards.add(new SetCardInfo("Stench of Evil", 165, Rarity.UNCOMMON, mage.cards.s.StenchOfEvil.class)); - cards.add(new SetCardInfo("Stone Rain", 217, Rarity.COMMON, mage.cards.s.StoneRain.class)); - cards.add(new SetCardInfo("Stone Spirit", 218, Rarity.UNCOMMON, mage.cards.s.StoneSpirit.class)); - cards.add(new SetCardInfo("Stonehands", 219, Rarity.COMMON, mage.cards.s.Stonehands.class)); - cards.add(new SetCardInfo("Storm Spirit", 303, Rarity.RARE, mage.cards.s.StormSpirit.class)); - cards.add(new SetCardInfo("Stormbind", 304, Rarity.RARE, mage.cards.s.Stormbind.class)); - cards.add(new SetCardInfo("Stromgald Cabal", 166, Rarity.RARE, mage.cards.s.StromgaldCabal.class)); - cards.add(new SetCardInfo("Stunted Growth", 266, Rarity.RARE, mage.cards.s.StuntedGrowth.class)); - cards.add(new SetCardInfo("Sulfurous Springs", 360, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); - cards.add(new SetCardInfo("Sunstone", 341, Rarity.UNCOMMON, mage.cards.s.Sunstone.class)); - cards.add(new SetCardInfo("Swamp", 373, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 374, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 375, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swords to Plowshares", 54, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); - cards.add(new SetCardInfo("Tarpan", 267, Rarity.COMMON, mage.cards.t.Tarpan.class)); - cards.add(new SetCardInfo("Thermokarst", 268, Rarity.UNCOMMON, mage.cards.t.Thermokarst.class)); - cards.add(new SetCardInfo("Thoughtleech", 269, Rarity.UNCOMMON, mage.cards.t.Thoughtleech.class)); - cards.add(new SetCardInfo("Thunder Wall", 104, Rarity.UNCOMMON, mage.cards.t.ThunderWall.class)); - cards.add(new SetCardInfo("Timberline Ridge", 361, Rarity.RARE, mage.cards.t.TimberlineRidge.class)); - cards.add(new SetCardInfo("Time Bomb", 342, Rarity.RARE, mage.cards.t.TimeBomb.class)); - cards.add(new SetCardInfo("Tinder Wall", 270, Rarity.COMMON, mage.cards.t.TinderWall.class)); - cards.add(new SetCardInfo("Tor Giant", 220, Rarity.COMMON, mage.cards.t.TorGiant.class)); - cards.add(new SetCardInfo("Total War", 221, Rarity.RARE, mage.cards.t.TotalWar.class)); - cards.add(new SetCardInfo("Touch of Death", 167, Rarity.COMMON, mage.cards.t.TouchOfDeath.class)); - cards.add(new SetCardInfo("Trailblazer", 272, Rarity.RARE, mage.cards.t.Trailblazer.class)); - cards.add(new SetCardInfo("Underground River", 362, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); - cards.add(new SetCardInfo("Updraft", 105, Rarity.UNCOMMON, mage.cards.u.Updraft.class)); - cards.add(new SetCardInfo("Urza's Bauble", 343, Rarity.UNCOMMON, mage.cards.u.UrzasBauble.class)); - cards.add(new SetCardInfo("Veldt", 363, Rarity.RARE, mage.cards.v.Veldt.class)); - cards.add(new SetCardInfo("Venomous Breath", 273, Rarity.UNCOMMON, mage.cards.v.VenomousBreath.class)); - cards.add(new SetCardInfo("Vertigo", 222, Rarity.UNCOMMON, mage.cards.v.Vertigo.class)); - cards.add(new SetCardInfo("Vexing Arcanix", 344, Rarity.RARE, mage.cards.v.VexingArcanix.class)); - cards.add(new SetCardInfo("Vibrating Sphere", 345, Rarity.RARE, mage.cards.v.VibratingSphere.class)); - cards.add(new SetCardInfo("Walking Wall", 346, Rarity.UNCOMMON, mage.cards.w.WalkingWall.class)); - cards.add(new SetCardInfo("Wall of Lava", 223, Rarity.UNCOMMON, mage.cards.w.WallOfLava.class)); - cards.add(new SetCardInfo("Wall of Pine Needles", 274, Rarity.UNCOMMON, mage.cards.w.WallOfPineNeedles.class)); - cards.add(new SetCardInfo("Wall of Shields", 347, Rarity.UNCOMMON, mage.cards.w.WallOfShields.class)); - cards.add(new SetCardInfo("War Chariot", 348, Rarity.UNCOMMON, mage.cards.w.WarChariot.class)); - cards.add(new SetCardInfo("Warning", 55, Rarity.COMMON, mage.cards.w.Warning.class)); - cards.add(new SetCardInfo("Whalebone Glider", 349, Rarity.UNCOMMON, mage.cards.w.WhaleboneGlider.class)); - cards.add(new SetCardInfo("White Scarab", 56, Rarity.UNCOMMON, mage.cards.w.WhiteScarab.class)); - cards.add(new SetCardInfo("Whiteout", 275, Rarity.UNCOMMON, mage.cards.w.Whiteout.class)); - cards.add(new SetCardInfo("Wiitigo", 276, Rarity.RARE, mage.cards.w.Wiitigo.class)); - cards.add(new SetCardInfo("Wild Growth", 277, Rarity.COMMON, mage.cards.w.WildGrowth.class)); - cards.add(new SetCardInfo("Wind Spirit", 106, Rarity.UNCOMMON, mage.cards.w.WindSpirit.class)); - cards.add(new SetCardInfo("Wings of Aesthir", 305, Rarity.UNCOMMON, mage.cards.w.WingsOfAesthir.class)); - cards.add(new SetCardInfo("Withering Wisps", 168, Rarity.UNCOMMON, mage.cards.w.WitheringWisps.class)); - cards.add(new SetCardInfo("Woolly Mammoths", 278, Rarity.COMMON, mage.cards.w.WoollyMammoths.class)); - cards.add(new SetCardInfo("Woolly Spider", 279, Rarity.COMMON, mage.cards.w.WoollySpider.class)); - cards.add(new SetCardInfo("Word of Blasting", 224, Rarity.UNCOMMON, mage.cards.w.WordOfBlasting.class)); - cards.add(new SetCardInfo("Word of Undoing", 108, Rarity.COMMON, mage.cards.w.WordOfUndoing.class)); - cards.add(new SetCardInfo("Wrath of Marit Lage", 109, Rarity.RARE, mage.cards.w.WrathOfMaritLage.class)); - cards.add(new SetCardInfo("Yavimaya Gnats", 280, Rarity.UNCOMMON, mage.cards.y.YavimayaGnats.class)); - cards.add(new SetCardInfo("Zur's Weirding", 110, Rarity.RARE, mage.cards.z.ZursWeirding.class)); - cards.add(new SetCardInfo("Zuran Enchanter", 111, Rarity.COMMON, mage.cards.z.ZuranEnchanter.class)); - cards.add(new SetCardInfo("Zuran Orb", 350, Rarity.UNCOMMON, mage.cards.z.ZuranOrb.class)); - cards.add(new SetCardInfo("Zuran Spellcaster", 112, Rarity.COMMON, mage.cards.z.ZuranSpellcaster.class)); - } -} +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author North + */ +public final class IceAge extends ExpansionSet { + + private static final IceAge instance = new IceAge(); + + public static IceAge getInstance() { + return instance; + } + + private IceAge() { + super("Ice Age", "ICE", ExpansionSet.buildDate(1995, 5, 1), SetType.EXPANSION); + this.blockName = "Ice Age"; + this.hasBoosters = true; + this.numBoosterLands = 0; + this.numBoosterCommon = 11; + this.numBoosterUncommon = 3; + this.numBoosterRare = 1; + this.ratioBoosterMythic = 0; + + cards.add(new SetCardInfo("Abyssal Specter", 113, Rarity.UNCOMMON, mage.cards.a.AbyssalSpecter.class)); + cards.add(new SetCardInfo("Adarkar Sentinel", 306, Rarity.UNCOMMON, mage.cards.a.AdarkarSentinel.class)); + cards.add(new SetCardInfo("Adarkar Wastes", 351, Rarity.RARE, mage.cards.a.AdarkarWastes.class)); + cards.add(new SetCardInfo("Aegis of the Meek", 307, Rarity.RARE, mage.cards.a.AegisOfTheMeek.class)); + cards.add(new SetCardInfo("Aggression", 169, Rarity.UNCOMMON, mage.cards.a.Aggression.class)); + 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("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)); + cards.add(new SetCardInfo("Ashen Ghoul", 114, Rarity.UNCOMMON, mage.cards.a.AshenGhoul.class)); + cards.add(new SetCardInfo("Aurochs", 225, Rarity.COMMON, mage.cards.a.Aurochs.class)); + cards.add(new SetCardInfo("Avalanche", 171, Rarity.UNCOMMON, mage.cards.a.Avalanche.class)); + cards.add(new SetCardInfo("Balduvian Barbarians", 172, Rarity.COMMON, mage.cards.b.BalduvianBarbarians.class)); + cards.add(new SetCardInfo("Balduvian Bears", 226, Rarity.COMMON, mage.cards.b.BalduvianBears.class)); + cards.add(new SetCardInfo("Balduvian Conjurer", 58, Rarity.UNCOMMON, mage.cards.b.BalduvianConjurer.class)); + cards.add(new SetCardInfo("Balduvian Hydra", 173, Rarity.RARE, mage.cards.b.BalduvianHydra.class)); + cards.add(new SetCardInfo("Barbed Sextant", 312, Rarity.COMMON, mage.cards.b.BarbedSextant.class)); + cards.add(new SetCardInfo("Baton of Morale", 313, Rarity.UNCOMMON, mage.cards.b.BatonOfMorale.class)); + cards.add(new SetCardInfo("Battle Cry", 5, Rarity.UNCOMMON, mage.cards.b.BattleCry.class)); + cards.add(new SetCardInfo("Battle Frenzy", 175, Rarity.COMMON, mage.cards.b.BattleFrenzy.class)); + cards.add(new SetCardInfo("Binding Grasp", 60, Rarity.UNCOMMON, mage.cards.b.BindingGrasp.class)); + cards.add(new SetCardInfo("Black Scarab", 6, Rarity.UNCOMMON, mage.cards.b.BlackScarab.class)); + cards.add(new SetCardInfo("Blessed Wine", 7, Rarity.COMMON, mage.cards.b.BlessedWine.class)); + cards.add(new SetCardInfo("Blinking Spirit", 8, Rarity.RARE, mage.cards.b.BlinkingSpirit.class)); + cards.add(new SetCardInfo("Blizzard", 227, Rarity.RARE, mage.cards.b.Blizzard.class)); + cards.add(new SetCardInfo("Blue Scarab", 9, Rarity.UNCOMMON, mage.cards.b.BlueScarab.class)); + cards.add(new SetCardInfo("Brainstorm", 61, Rarity.COMMON, mage.cards.b.Brainstorm.class)); + cards.add(new SetCardInfo("Brand of Ill Omen", 177, Rarity.RARE, mage.cards.b.BrandOfIllOmen.class)); + cards.add(new SetCardInfo("Breath of Dreams", 62, Rarity.UNCOMMON, mage.cards.b.BreathOfDreams.class)); + cards.add(new SetCardInfo("Brine Shaman", 115, Rarity.COMMON, mage.cards.b.BrineShaman.class)); + cards.add(new SetCardInfo("Brown Ouphe", 228, Rarity.COMMON, mage.cards.b.BrownOuphe.class)); + cards.add(new SetCardInfo("Brushland", 352, Rarity.RARE, mage.cards.b.Brushland.class)); + cards.add(new SetCardInfo("Burnt Offering", 116, Rarity.COMMON, mage.cards.b.BurntOffering.class)); + cards.add(new SetCardInfo("Call to Arms", 10, Rarity.RARE, mage.cards.c.CallToArms.class)); + cards.add(new SetCardInfo("Caribou Range", 11, Rarity.RARE, mage.cards.c.CaribouRange.class)); + cards.add(new SetCardInfo("Celestial Sword", 314, Rarity.RARE, mage.cards.c.CelestialSword.class)); + cards.add(new SetCardInfo("Centaur Archer", 282, Rarity.UNCOMMON, mage.cards.c.CentaurArcher.class)); + cards.add(new SetCardInfo("Chaos Lord", 178, Rarity.RARE, mage.cards.c.ChaosLord.class)); + cards.add(new SetCardInfo("Chaos Moon", 179, Rarity.RARE, mage.cards.c.ChaosMoon.class)); + cards.add(new SetCardInfo("Chub Toad", 229, Rarity.COMMON, mage.cards.c.ChubToad.class)); + cards.add(new SetCardInfo("Circle of Protection: Black", 12, Rarity.COMMON, mage.cards.c.CircleOfProtectionBlack.class)); + cards.add(new SetCardInfo("Circle of Protection: Blue", 13, Rarity.COMMON, mage.cards.c.CircleOfProtectionBlue.class)); + cards.add(new SetCardInfo("Circle of Protection: Green", 14, Rarity.COMMON, mage.cards.c.CircleOfProtectionGreen.class)); + cards.add(new SetCardInfo("Circle of Protection: Red", 15, Rarity.COMMON, mage.cards.c.CircleOfProtectionRed.class)); + cards.add(new SetCardInfo("Circle of Protection: White", 16, Rarity.COMMON, mage.cards.c.CircleOfProtectionWhite.class)); + cards.add(new SetCardInfo("Clairvoyance", 63, Rarity.COMMON, mage.cards.c.Clairvoyance.class)); + cards.add(new SetCardInfo("Cloak of Confusion", 117, Rarity.COMMON, mage.cards.c.CloakOfConfusion.class)); + cards.add(new SetCardInfo("Cold Snap", 17, Rarity.UNCOMMON, mage.cards.c.ColdSnap.class)); + cards.add(new SetCardInfo("Conquer", 180, Rarity.UNCOMMON, mage.cards.c.Conquer.class)); + cards.add(new SetCardInfo("Cooperation", 18, Rarity.COMMON, mage.cards.c.Cooperation.class)); + cards.add(new SetCardInfo("Counterspell", 64, Rarity.COMMON, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Crown of the Ages", 315, Rarity.RARE, mage.cards.c.CrownOfTheAges.class)); + cards.add(new SetCardInfo("Curse of Marit Lage", 181, Rarity.RARE, mage.cards.c.CurseOfMaritLage.class)); + cards.add(new SetCardInfo("Dance of the Dead", 118, Rarity.UNCOMMON, mage.cards.d.DanceOfTheDead.class)); + cards.add(new SetCardInfo("Dark Banishing", 119, Rarity.COMMON, mage.cards.d.DarkBanishing.class)); + cards.add(new SetCardInfo("Dark Ritual", 120, Rarity.COMMON, mage.cards.d.DarkRitual.class)); + cards.add(new SetCardInfo("Death Ward", 19, Rarity.COMMON, mage.cards.d.DeathWard.class)); + cards.add(new SetCardInfo("Deflection", 65, Rarity.RARE, mage.cards.d.Deflection.class)); + cards.add(new SetCardInfo("Demonic Consultation", 121, Rarity.UNCOMMON, mage.cards.d.DemonicConsultation.class)); + cards.add(new SetCardInfo("Despotic Scepter", 316, Rarity.RARE, mage.cards.d.DespoticScepter.class)); + cards.add(new SetCardInfo("Diabolic Vision", 284, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class)); + cards.add(new SetCardInfo("Dire Wolves", 230, Rarity.COMMON, mage.cards.d.DireWolves.class)); + cards.add(new SetCardInfo("Disenchant", 20, Rarity.COMMON, mage.cards.d.Disenchant.class)); + cards.add(new SetCardInfo("Dread Wight", 122, Rarity.RARE, mage.cards.d.DreadWight.class)); + cards.add(new SetCardInfo("Dreams of the Dead", 66, Rarity.UNCOMMON, mage.cards.d.DreamsOfTheDead.class)); + cards.add(new SetCardInfo("Drift of the Dead", 123, Rarity.UNCOMMON, mage.cards.d.DriftOfTheDead.class)); + cards.add(new SetCardInfo("Drought", 21, Rarity.UNCOMMON, mage.cards.d.Drought.class)); + cards.add(new SetCardInfo("Dwarven Armory", 182, Rarity.RARE, mage.cards.d.DwarvenArmory.class)); + cards.add(new SetCardInfo("Earthlink", 285, Rarity.RARE, mage.cards.e.Earthlink.class)); + cards.add(new SetCardInfo("Earthlore", 231, Rarity.COMMON, mage.cards.e.Earthlore.class)); + cards.add(new SetCardInfo("Elder Druid", 232, Rarity.RARE, mage.cards.e.ElderDruid.class)); + cards.add(new SetCardInfo("Elemental Augury", 286, Rarity.RARE, mage.cards.e.ElementalAugury.class)); + cards.add(new SetCardInfo("Elkin Bottle", 317, Rarity.RARE, mage.cards.e.ElkinBottle.class)); + cards.add(new SetCardInfo("Enduring Renewal", 23, Rarity.RARE, mage.cards.e.EnduringRenewal.class)); + cards.add(new SetCardInfo("Energy Storm", 24, Rarity.RARE, mage.cards.e.EnergyStorm.class)); + cards.add(new SetCardInfo("Enervate", 67, Rarity.COMMON, mage.cards.e.Enervate.class)); + cards.add(new SetCardInfo("Errant Minion", 68, Rarity.COMMON, mage.cards.e.ErrantMinion.class)); + cards.add(new SetCardInfo("Errantry", 183, Rarity.COMMON, mage.cards.e.Errantry.class)); + cards.add(new SetCardInfo("Essence Filter", 233, Rarity.COMMON, mage.cards.e.EssenceFilter.class)); + cards.add(new SetCardInfo("Essence Flare", 69, Rarity.COMMON, mage.cards.e.EssenceFlare.class)); + cards.add(new SetCardInfo("Fanatical Fever", 234, Rarity.UNCOMMON, mage.cards.f.FanaticalFever.class)); + cards.add(new SetCardInfo("Fear", 124, Rarity.COMMON, mage.cards.f.Fear.class)); + cards.add(new SetCardInfo("Fiery Justice", 288, Rarity.RARE, mage.cards.f.FieryJustice.class)); + cards.add(new SetCardInfo("Fire Covenant", 289, Rarity.UNCOMMON, mage.cards.f.FireCovenant.class)); + cards.add(new SetCardInfo("Flame Spirit", 184, Rarity.UNCOMMON, mage.cards.f.FlameSpirit.class)); + cards.add(new SetCardInfo("Flare", 185, Rarity.COMMON, mage.cards.f.Flare.class)); + cards.add(new SetCardInfo("Flooded Woodlands", 290, Rarity.RARE, mage.cards.f.FloodedWoodlands.class)); + cards.add(new SetCardInfo("Flow of Maggots", 125, Rarity.RARE, mage.cards.f.FlowOfMaggots.class)); + cards.add(new SetCardInfo("Folk of the Pines", 235, Rarity.COMMON, mage.cards.f.FolkOfThePines.class)); + cards.add(new SetCardInfo("Forbidden Lore", 236, Rarity.RARE, mage.cards.f.ForbiddenLore.class)); + cards.add(new SetCardInfo("Force Void", 70, Rarity.UNCOMMON, mage.cards.f.ForceVoid.class)); + cards.add(new SetCardInfo("Forest", 380, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 381, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 382, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forgotten Lore", 237, Rarity.UNCOMMON, mage.cards.f.ForgottenLore.class)); + cards.add(new SetCardInfo("Formation", 25, Rarity.RARE, mage.cards.f.Formation.class)); + cards.add(new SetCardInfo("Foul Familiar", 126, Rarity.COMMON, mage.cards.f.FoulFamiliar.class)); + cards.add(new SetCardInfo("Foxfire", 238, Rarity.COMMON, mage.cards.f.Foxfire.class)); + cards.add(new SetCardInfo("Freyalise Supplicant", 239, Rarity.UNCOMMON, mage.cards.f.FreyaliseSupplicant.class)); + cards.add(new SetCardInfo("Freyalise's Charm", 240, Rarity.UNCOMMON, mage.cards.f.FreyalisesCharm.class)); + cards.add(new SetCardInfo("Freyalise's Winds", 241, Rarity.RARE, mage.cards.f.FreyalisesWinds.class)); + cards.add(new SetCardInfo("Fumarole", 291, Rarity.UNCOMMON, mage.cards.f.Fumarole.class)); + cards.add(new SetCardInfo("Fylgja", 26, Rarity.COMMON, mage.cards.f.Fylgja.class)); + cards.add(new SetCardInfo("Fyndhorn Bow", 318, Rarity.UNCOMMON, mage.cards.f.FyndhornBow.class)); + cards.add(new SetCardInfo("Fyndhorn Brownie", 242, Rarity.COMMON, mage.cards.f.FyndhornBrownie.class)); + cards.add(new SetCardInfo("Fyndhorn Elder", 243, Rarity.UNCOMMON, mage.cards.f.FyndhornElder.class)); + cards.add(new SetCardInfo("Fyndhorn Elves", 244, Rarity.COMMON, mage.cards.f.FyndhornElves.class)); + cards.add(new SetCardInfo("Fyndhorn Pollen", 245, Rarity.RARE, mage.cards.f.FyndhornPollen.class)); + cards.add(new SetCardInfo("Game of Chaos", 186, Rarity.RARE, mage.cards.g.GameOfChaos.class)); + cards.add(new SetCardInfo("Gangrenous Zombies", 127, Rarity.COMMON, mage.cards.g.GangrenousZombies.class)); + cards.add(new SetCardInfo("Gaze of Pain", 128, Rarity.COMMON, mage.cards.g.GazeOfPain.class)); + cards.add(new SetCardInfo("General Jarkeld", 27, Rarity.RARE, mage.cards.g.GeneralJarkeld.class)); + cards.add(new SetCardInfo("Giant Growth", 246, Rarity.COMMON, mage.cards.g.GiantGrowth.class)); + cards.add(new SetCardInfo("Giant Trap Door Spider", 293, Rarity.UNCOMMON, mage.cards.g.GiantTrapDoorSpider.class)); + cards.add(new SetCardInfo("Glacial Chasm", 353, Rarity.UNCOMMON, mage.cards.g.GlacialChasm.class)); + cards.add(new SetCardInfo("Glacial Crevasses", 187, Rarity.RARE, mage.cards.g.GlacialCrevasses.class)); + cards.add(new SetCardInfo("Glacial Wall", 71, Rarity.UNCOMMON, mage.cards.g.GlacialWall.class)); + cards.add(new SetCardInfo("Glaciers", 294, Rarity.RARE, mage.cards.g.Glaciers.class)); + cards.add(new SetCardInfo("Goblin Lyre", 319, Rarity.RARE, mage.cards.g.GoblinLyre.class)); + cards.add(new SetCardInfo("Goblin Mutant", 188, Rarity.UNCOMMON, mage.cards.g.GoblinMutant.class)); + cards.add(new SetCardInfo("Goblin Snowman", 191, Rarity.UNCOMMON, mage.cards.g.GoblinSnowman.class)); + cards.add(new SetCardInfo("Gorilla Pack", 247, Rarity.COMMON, mage.cards.g.GorillaPack.class)); + cards.add(new SetCardInfo("Gravebind", 129, Rarity.RARE, mage.cards.g.Gravebind.class)); + cards.add(new SetCardInfo("Green Scarab", 28, Rarity.UNCOMMON, mage.cards.g.GreenScarab.class)); + cards.add(new SetCardInfo("Hallowed Ground", 29, Rarity.UNCOMMON, mage.cards.h.HallowedGround.class)); + cards.add(new SetCardInfo("Halls of Mist", 354, Rarity.RARE, mage.cards.h.HallsOfMist.class)); + cards.add(new SetCardInfo("Heal", 30, Rarity.COMMON, mage.cards.h.Heal.class)); + cards.add(new SetCardInfo("Hecatomb", 130, Rarity.RARE, mage.cards.h.Hecatomb.class)); + cards.add(new SetCardInfo("Hematite Talisman", 320, Rarity.UNCOMMON, mage.cards.h.HematiteTalisman.class)); + cards.add(new SetCardInfo("Hoar Shade", 131, Rarity.COMMON, mage.cards.h.HoarShade.class)); + cards.add(new SetCardInfo("Hot Springs", 248, Rarity.RARE, mage.cards.h.HotSprings.class)); + cards.add(new SetCardInfo("Howl from Beyond", 132, Rarity.COMMON, mage.cards.h.HowlFromBeyond.class)); + cards.add(new SetCardInfo("Hurricane", 249, Rarity.UNCOMMON, mage.cards.h.Hurricane.class)); + cards.add(new SetCardInfo("Hyalopterous Lemure", 133, Rarity.UNCOMMON, mage.cards.h.HyalopterousLemure.class)); + cards.add(new SetCardInfo("Hydroblast", 72, Rarity.COMMON, mage.cards.h.Hydroblast.class)); + cards.add(new SetCardInfo("Hymn of Rebirth", 295, Rarity.UNCOMMON, mage.cards.h.HymnOfRebirth.class)); + cards.add(new SetCardInfo("Ice Cauldron", 321, Rarity.RARE, mage.cards.i.IceCauldron.class)); + cards.add(new SetCardInfo("Ice Floe", 355, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); + cards.add(new SetCardInfo("Iceberg", 73, Rarity.UNCOMMON, mage.cards.i.Iceberg.class)); + cards.add(new SetCardInfo("Icequake", 134, Rarity.UNCOMMON, mage.cards.i.Icequake.class)); + cards.add(new SetCardInfo("Icy Manipulator", 322, Rarity.UNCOMMON, mage.cards.i.IcyManipulator.class)); + cards.add(new SetCardInfo("Icy Prison", 74, Rarity.RARE, mage.cards.i.IcyPrison.class)); + cards.add(new SetCardInfo("Illusionary Forces", 75, Rarity.COMMON, mage.cards.i.IllusionaryForces.class)); + cards.add(new SetCardInfo("Illusionary Presence", 76, Rarity.RARE, mage.cards.i.IllusionaryPresence.class)); + cards.add(new SetCardInfo("Illusionary Terrain", 77, Rarity.UNCOMMON, mage.cards.i.IllusionaryTerrain.class)); + cards.add(new SetCardInfo("Illusionary Wall", 78, Rarity.COMMON, mage.cards.i.IllusionaryWall.class)); + cards.add(new SetCardInfo("Illusions of Grandeur", 79, Rarity.RARE, mage.cards.i.IllusionsOfGrandeur.class)); + cards.add(new SetCardInfo("Imposing Visage", 193, Rarity.COMMON, mage.cards.i.ImposingVisage.class)); + cards.add(new SetCardInfo("Incinerate", 194, Rarity.COMMON, mage.cards.i.Incinerate.class)); + cards.add(new SetCardInfo("Infernal Darkness", 135, Rarity.RARE, mage.cards.i.InfernalDarkness.class)); + cards.add(new SetCardInfo("Infernal Denizen", 136, Rarity.RARE, mage.cards.i.InfernalDenizen.class)); + cards.add(new SetCardInfo("Infinite Hourglass", 323, Rarity.RARE, mage.cards.i.InfiniteHourglass.class)); + cards.add(new SetCardInfo("Infuse", 80, Rarity.COMMON, mage.cards.i.Infuse.class)); + cards.add(new SetCardInfo("Island", 368, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 369, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 370, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jester's Cap", 324, Rarity.RARE, mage.cards.j.JestersCap.class)); + cards.add(new SetCardInfo("Jester's Mask", 325, Rarity.RARE, mage.cards.j.JestersMask.class)); + cards.add(new SetCardInfo("Jeweled Amulet", 326, Rarity.UNCOMMON, mage.cards.j.JeweledAmulet.class)); + cards.add(new SetCardInfo("Johtull Wurm", 250, Rarity.UNCOMMON, mage.cards.j.JohtullWurm.class)); + cards.add(new SetCardInfo("Jokulhaups", 195, Rarity.RARE, mage.cards.j.Jokulhaups.class)); + cards.add(new SetCardInfo("Juniper Order Druid", 251, Rarity.COMMON, mage.cards.j.JuniperOrderDruid.class)); + cards.add(new SetCardInfo("Justice", 32, Rarity.UNCOMMON, mage.cards.j.Justice.class)); + cards.add(new SetCardInfo("Karplusan Forest", 356, Rarity.RARE, mage.cards.k.KarplusanForest.class)); + cards.add(new SetCardInfo("Karplusan Giant", 196, Rarity.UNCOMMON, mage.cards.k.KarplusanGiant.class)); + cards.add(new SetCardInfo("Karplusan Yeti", 197, Rarity.RARE, mage.cards.k.KarplusanYeti.class)); + cards.add(new SetCardInfo("Kelsinko Ranger", 33, Rarity.COMMON, mage.cards.k.KelsinkoRanger.class)); + cards.add(new SetCardInfo("Kjeldoran Dead", 137, Rarity.COMMON, mage.cards.k.KjeldoranDead.class)); + cards.add(new SetCardInfo("Kjeldoran Frostbeast", 296, Rarity.UNCOMMON, mage.cards.k.KjeldoranFrostbeast.class)); + cards.add(new SetCardInfo("Kjeldoran Knight", 36, Rarity.RARE, mage.cards.k.KjeldoranKnight.class)); + cards.add(new SetCardInfo("Kjeldoran Phalanx", 37, Rarity.RARE, mage.cards.k.KjeldoranPhalanx.class)); + cards.add(new SetCardInfo("Kjeldoran Royal Guard", 38, Rarity.RARE, mage.cards.k.KjeldoranRoyalGuard.class)); + cards.add(new SetCardInfo("Kjeldoran Skycaptain", 39, Rarity.UNCOMMON, mage.cards.k.KjeldoranSkycaptain.class)); + cards.add(new SetCardInfo("Kjeldoran Skyknight", 40, Rarity.COMMON, mage.cards.k.KjeldoranSkyknight.class)); + cards.add(new SetCardInfo("Kjeldoran Warrior", 41, Rarity.COMMON, mage.cards.k.KjeldoranWarrior.class)); + cards.add(new SetCardInfo("Knight of Stromgald", 138, Rarity.UNCOMMON, mage.cards.k.KnightOfStromgald.class)); + cards.add(new SetCardInfo("Krovikan Elementalist", 139, Rarity.UNCOMMON, mage.cards.k.KrovikanElementalist.class)); + cards.add(new SetCardInfo("Krovikan Fetish", 140, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); + cards.add(new SetCardInfo("Krovikan Sorcerer", 81, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); + cards.add(new SetCardInfo("Krovikan Vampire", 141, Rarity.UNCOMMON, mage.cards.k.KrovikanVampire.class)); + cards.add(new SetCardInfo("Land Cap", 357, Rarity.RARE, mage.cards.l.LandCap.class)); + cards.add(new SetCardInfo("Lapis Lazuli Talisman", 327, Rarity.UNCOMMON, mage.cards.l.LapisLazuliTalisman.class)); + cards.add(new SetCardInfo("Lava Tubes", 358, Rarity.RARE, mage.cards.l.LavaTubes.class)); + cards.add(new SetCardInfo("Legions of Lim-Dul", 142, Rarity.COMMON, mage.cards.l.LegionsOfLimDul.class)); + cards.add(new SetCardInfo("Leshrac's Rite", 143, Rarity.UNCOMMON, mage.cards.l.LeshracsRite.class)); + cards.add(new SetCardInfo("Leshrac's Sigil", 144, Rarity.UNCOMMON, mage.cards.l.LeshracsSigil.class)); + cards.add(new SetCardInfo("Lhurgoyf", 252, Rarity.RARE, mage.cards.l.Lhurgoyf.class)); + cards.add(new SetCardInfo("Lightning Blow", 42, Rarity.RARE, mage.cards.l.LightningBlow.class)); + cards.add(new SetCardInfo("Lim-Dul's Cohort", 145, Rarity.COMMON, mage.cards.l.LimDulsCohort.class)); + cards.add(new SetCardInfo("Lim-Dul's Hex", 146, Rarity.UNCOMMON, mage.cards.l.LimDulsHex.class)); + cards.add(new SetCardInfo("Lost Order of Jarkeld", 43, Rarity.RARE, mage.cards.l.LostOrderOfJarkeld.class)); + cards.add(new SetCardInfo("Lure", 253, Rarity.UNCOMMON, mage.cards.l.Lure.class)); + cards.add(new SetCardInfo("Magus of the Unseen", 82, Rarity.RARE, mage.cards.m.MagusOfTheUnseen.class)); + cards.add(new SetCardInfo("Malachite Talisman", 328, Rarity.UNCOMMON, mage.cards.m.MalachiteTalisman.class)); + cards.add(new SetCardInfo("Marton Stromgald", 199, Rarity.RARE, mage.cards.m.MartonStromgald.class)); + cards.add(new SetCardInfo("Melee", 200, Rarity.UNCOMMON, mage.cards.m.Melee.class)); + cards.add(new SetCardInfo("Melting", 201, Rarity.UNCOMMON, mage.cards.m.Melting.class)); + cards.add(new SetCardInfo("Merieke Ri Berit", 297, Rarity.RARE, mage.cards.m.MeriekeRiBerit.class)); + cards.add(new SetCardInfo("Mesmeric Trance", 83, Rarity.RARE, mage.cards.m.MesmericTrance.class)); + cards.add(new SetCardInfo("Meteor Shower", 202, Rarity.COMMON, mage.cards.m.MeteorShower.class)); + cards.add(new SetCardInfo("Mind Ravel", 147, Rarity.COMMON, mage.cards.m.MindRavel.class)); + cards.add(new SetCardInfo("Mind Warp", 148, Rarity.UNCOMMON, mage.cards.m.MindWarp.class)); + cards.add(new SetCardInfo("Mind Whip", 149, Rarity.RARE, mage.cards.m.MindWhip.class)); + cards.add(new SetCardInfo("Minion of Leshrac", 150, Rarity.RARE, mage.cards.m.MinionOfLeshrac.class)); + cards.add(new SetCardInfo("Minion of Tevesh Szat", 151, Rarity.RARE, mage.cards.m.MinionOfTeveshSzat.class)); + cards.add(new SetCardInfo("Mistfolk", 84, Rarity.COMMON, mage.cards.m.Mistfolk.class)); + cards.add(new SetCardInfo("Mole Worms", 152, Rarity.UNCOMMON, mage.cards.m.MoleWorms.class)); + cards.add(new SetCardInfo("Monsoon", 298, Rarity.RARE, mage.cards.m.Monsoon.class)); + cards.add(new SetCardInfo("Moor Fiend", 153, Rarity.COMMON, mage.cards.m.MoorFiend.class)); + cards.add(new SetCardInfo("Mountain Goat", 203, Rarity.COMMON, mage.cards.m.MountainGoat.class)); + cards.add(new SetCardInfo("Mountain Titan", 299, Rarity.RARE, mage.cards.m.MountainTitan.class)); + cards.add(new SetCardInfo("Mountain", 376, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 377, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 378, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mudslide", 204, Rarity.RARE, mage.cards.m.Mudslide.class)); + cards.add(new SetCardInfo("Musician", 85, Rarity.RARE, mage.cards.m.Musician.class)); + cards.add(new SetCardInfo("Mystic Might", 86, Rarity.RARE, mage.cards.m.MysticMight.class)); + cards.add(new SetCardInfo("Mystic Remora", 87, Rarity.COMMON, mage.cards.m.MysticRemora.class)); + cards.add(new SetCardInfo("Nacre Talisman", 329, Rarity.UNCOMMON, mage.cards.n.NacreTalisman.class)); + cards.add(new SetCardInfo("Naked Singularity", 330, Rarity.RARE, mage.cards.n.NakedSingularity.class)); + cards.add(new SetCardInfo("Nature's Lore", 255, Rarity.UNCOMMON, mage.cards.n.NaturesLore.class)); + cards.add(new SetCardInfo("Necropotence", 154, Rarity.RARE, mage.cards.n.Necropotence.class)); + cards.add(new SetCardInfo("Norritt", 155, Rarity.COMMON, mage.cards.n.Norritt.class)); + cards.add(new SetCardInfo("Oath of Lim-Dul", 156, Rarity.RARE, mage.cards.o.OathOfLimDul.class)); + cards.add(new SetCardInfo("Onyx Talisman", 331, Rarity.UNCOMMON, mage.cards.o.OnyxTalisman.class)); + cards.add(new SetCardInfo("Orcish Cannoneers", 205, Rarity.UNCOMMON, mage.cards.o.OrcishCannoneers.class)); + cards.add(new SetCardInfo("Orcish Healer", 208, Rarity.UNCOMMON, mage.cards.o.OrcishHealer.class)); + cards.add(new SetCardInfo("Orcish Librarian", 209, Rarity.RARE, mage.cards.o.OrcishLibrarian.class)); + cards.add(new SetCardInfo("Orcish Lumberjack", 210, Rarity.COMMON, mage.cards.o.OrcishLumberjack.class)); + cards.add(new SetCardInfo("Orcish Squatters", 211, Rarity.RARE, mage.cards.o.OrcishSquatters.class)); + cards.add(new SetCardInfo("Order of the Sacred Torch", 45, Rarity.RARE, mage.cards.o.OrderOfTheSacredTorch.class)); + cards.add(new SetCardInfo("Order of the White Shield", 46, Rarity.UNCOMMON, mage.cards.o.OrderOfTheWhiteShield.class)); + cards.add(new SetCardInfo("Pale Bears", 256, Rarity.RARE, mage.cards.p.PaleBears.class)); + cards.add(new SetCardInfo("Panic", 212, Rarity.COMMON, mage.cards.p.Panic.class)); + cards.add(new SetCardInfo("Pentagram of the Ages", 332, Rarity.RARE, mage.cards.p.PentagramOfTheAges.class)); + cards.add(new SetCardInfo("Pestilence Rats", 157, Rarity.COMMON, mage.cards.p.PestilenceRats.class)); + cards.add(new SetCardInfo("Phantasmal Mount", 88, Rarity.UNCOMMON, mage.cards.p.PhantasmalMount.class)); + cards.add(new SetCardInfo("Pit Trap", 333, Rarity.UNCOMMON, mage.cards.p.PitTrap.class)); + cards.add(new SetCardInfo("Plains", 364, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 365, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 366, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Polar Kraken", 89, Rarity.RARE, mage.cards.p.PolarKraken.class)); + cards.add(new SetCardInfo("Portent", 90, Rarity.COMMON, mage.cards.p.Portent.class)); + cards.add(new SetCardInfo("Power Sink", 91, Rarity.COMMON, mage.cards.p.PowerSink.class)); + cards.add(new SetCardInfo("Pox", 158, Rarity.RARE, mage.cards.p.Pox.class)); + cards.add(new SetCardInfo("Prismatic Ward", 47, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); + cards.add(new SetCardInfo("Pygmy Allosaurus", 257, Rarity.RARE, mage.cards.p.PygmyAllosaurus.class)); + cards.add(new SetCardInfo("Pyknite", 258, Rarity.COMMON, mage.cards.p.Pyknite.class)); + cards.add(new SetCardInfo("Pyroblast", 213, Rarity.COMMON, mage.cards.p.Pyroblast.class)); + cards.add(new SetCardInfo("Pyroclasm", 214, Rarity.UNCOMMON, mage.cards.p.Pyroclasm.class)); + cards.add(new SetCardInfo("Rally", 48, Rarity.COMMON, mage.cards.r.Rally.class)); + cards.add(new SetCardInfo("Ray of Command", 92, Rarity.COMMON, mage.cards.r.RayOfCommand.class)); + cards.add(new SetCardInfo("Ray of Erasure", 93, Rarity.COMMON, mage.cards.r.RayOfErasure.class)); + cards.add(new SetCardInfo("Reality Twist", 94, Rarity.RARE, mage.cards.r.RealityTwist.class)); + cards.add(new SetCardInfo("Reclamation", 300, Rarity.RARE, mage.cards.r.Reclamation.class)); + cards.add(new SetCardInfo("Red Scarab", 49, Rarity.UNCOMMON, mage.cards.r.RedScarab.class)); + cards.add(new SetCardInfo("Regeneration", 259, Rarity.COMMON, mage.cards.r.Regeneration.class)); + cards.add(new SetCardInfo("Rime Dryad", 260, Rarity.COMMON, mage.cards.r.RimeDryad.class)); + cards.add(new SetCardInfo("Ritual of Subdual", 261, Rarity.RARE, mage.cards.r.RitualOfSubdual.class)); + cards.add(new SetCardInfo("River Delta", 359, Rarity.RARE, mage.cards.r.RiverDelta.class)); + cards.add(new SetCardInfo("Runed Arch", 334, Rarity.RARE, mage.cards.r.RunedArch.class)); + cards.add(new SetCardInfo("Sabretooth Tiger", 215, Rarity.COMMON, mage.cards.s.SabretoothTiger.class)); + cards.add(new SetCardInfo("Scaled Wurm", 262, Rarity.COMMON, mage.cards.s.ScaledWurm.class)); + cards.add(new SetCardInfo("Sea Spirit", 95, Rarity.UNCOMMON, mage.cards.s.SeaSpirit.class)); + cards.add(new SetCardInfo("Seizures", 159, Rarity.COMMON, mage.cards.s.Seizures.class)); + cards.add(new SetCardInfo("Seraph", 51, Rarity.RARE, mage.cards.s.Seraph.class)); + cards.add(new SetCardInfo("Shambling Strider", 263, Rarity.COMMON, mage.cards.s.ShamblingStrider.class)); + cards.add(new SetCardInfo("Shatter", 216, Rarity.COMMON, mage.cards.s.Shatter.class)); + cards.add(new SetCardInfo("Shield Bearer", 52, Rarity.COMMON, mage.cards.s.ShieldBearer.class)); + cards.add(new SetCardInfo("Shield of the Ages", 335, Rarity.UNCOMMON, mage.cards.s.ShieldOfTheAges.class)); + cards.add(new SetCardInfo("Shyft", 96, Rarity.RARE, mage.cards.s.Shyft.class)); + cards.add(new SetCardInfo("Sibilant Spirit", 97, Rarity.RARE, mage.cards.s.SibilantSpirit.class)); + cards.add(new SetCardInfo("Silver Erne", 98, Rarity.UNCOMMON, mage.cards.s.SilverErne.class)); + cards.add(new SetCardInfo("Skeleton Ship", 301, Rarity.RARE, mage.cards.s.SkeletonShip.class)); + cards.add(new SetCardInfo("Skull Catapult", 336, Rarity.UNCOMMON, mage.cards.s.SkullCatapult.class)); + cards.add(new SetCardInfo("Snow Devil", 100, Rarity.COMMON, mage.cards.s.SnowDevil.class)); + cards.add(new SetCardInfo("Snow Fortress", 337, Rarity.RARE, mage.cards.s.SnowFortress.class)); + cards.add(new SetCardInfo("Snow Hound", 53, Rarity.UNCOMMON, mage.cards.s.SnowHound.class)); + cards.add(new SetCardInfo("Snow-Covered Forest", 383, Rarity.LAND, mage.cards.s.SnowCoveredForest.class)); + cards.add(new SetCardInfo("Snow-Covered Island", 371, Rarity.LAND, mage.cards.s.SnowCoveredIsland.class)); + cards.add(new SetCardInfo("Snow-Covered Mountain", 379, Rarity.LAND, mage.cards.s.SnowCoveredMountain.class)); + cards.add(new SetCardInfo("Snow-Covered Plains", 367, Rarity.LAND, mage.cards.s.SnowCoveredPlains.class)); + cards.add(new SetCardInfo("Snow-Covered Swamp", 372, Rarity.LAND, mage.cards.s.SnowCoveredSwamp.class)); + cards.add(new SetCardInfo("Soldevi Golem", 338, Rarity.RARE, mage.cards.s.SoldeviGolem.class)); + cards.add(new SetCardInfo("Soldevi Machinist", 102, Rarity.UNCOMMON, mage.cards.s.SoldeviMachinist.class)); + cards.add(new SetCardInfo("Soldevi Simulacrum", 339, Rarity.UNCOMMON, mage.cards.s.SoldeviSimulacrum.class)); + cards.add(new SetCardInfo("Songs of the Damned", 160, Rarity.COMMON, mage.cards.s.SongsOfTheDamned.class)); + cards.add(new SetCardInfo("Soul Barrier", 103, Rarity.UNCOMMON, mage.cards.s.SoulBarrier.class)); + cards.add(new SetCardInfo("Soul Burn", 161, Rarity.COMMON, mage.cards.s.SoulBurn.class)); + cards.add(new SetCardInfo("Soul Kiss", 162, Rarity.COMMON, mage.cards.s.SoulKiss.class)); + cards.add(new SetCardInfo("Spoils of Evil", 163, Rarity.RARE, mage.cards.s.SpoilsOfEvil.class)); + cards.add(new SetCardInfo("Staff of the Ages", 340, Rarity.RARE, mage.cards.s.StaffOfTheAges.class)); + cards.add(new SetCardInfo("Stampede", 265, Rarity.RARE, mage.cards.s.Stampede.class)); + cards.add(new SetCardInfo("Stench of Evil", 165, Rarity.UNCOMMON, mage.cards.s.StenchOfEvil.class)); + cards.add(new SetCardInfo("Stone Rain", 217, Rarity.COMMON, mage.cards.s.StoneRain.class)); + cards.add(new SetCardInfo("Stone Spirit", 218, Rarity.UNCOMMON, mage.cards.s.StoneSpirit.class)); + cards.add(new SetCardInfo("Stonehands", 219, Rarity.COMMON, mage.cards.s.Stonehands.class)); + cards.add(new SetCardInfo("Storm Spirit", 303, Rarity.RARE, mage.cards.s.StormSpirit.class)); + cards.add(new SetCardInfo("Stormbind", 304, Rarity.RARE, mage.cards.s.Stormbind.class)); + cards.add(new SetCardInfo("Stromgald Cabal", 166, Rarity.RARE, mage.cards.s.StromgaldCabal.class)); + cards.add(new SetCardInfo("Stunted Growth", 266, Rarity.RARE, mage.cards.s.StuntedGrowth.class)); + cards.add(new SetCardInfo("Sulfurous Springs", 360, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); + cards.add(new SetCardInfo("Sunstone", 341, Rarity.UNCOMMON, mage.cards.s.Sunstone.class)); + cards.add(new SetCardInfo("Swamp", 373, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 374, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 375, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swords to Plowshares", 54, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Tarpan", 267, Rarity.COMMON, mage.cards.t.Tarpan.class)); + cards.add(new SetCardInfo("Thermokarst", 268, Rarity.UNCOMMON, mage.cards.t.Thermokarst.class)); + cards.add(new SetCardInfo("Thoughtleech", 269, Rarity.UNCOMMON, mage.cards.t.Thoughtleech.class)); + cards.add(new SetCardInfo("Thunder Wall", 104, Rarity.UNCOMMON, mage.cards.t.ThunderWall.class)); + cards.add(new SetCardInfo("Timberline Ridge", 361, Rarity.RARE, mage.cards.t.TimberlineRidge.class)); + cards.add(new SetCardInfo("Time Bomb", 342, Rarity.RARE, mage.cards.t.TimeBomb.class)); + cards.add(new SetCardInfo("Tinder Wall", 270, Rarity.COMMON, mage.cards.t.TinderWall.class)); + cards.add(new SetCardInfo("Tor Giant", 220, Rarity.COMMON, mage.cards.t.TorGiant.class)); + cards.add(new SetCardInfo("Total War", 221, Rarity.RARE, mage.cards.t.TotalWar.class)); + cards.add(new SetCardInfo("Touch of Death", 167, Rarity.COMMON, mage.cards.t.TouchOfDeath.class)); + cards.add(new SetCardInfo("Trailblazer", 272, Rarity.RARE, mage.cards.t.Trailblazer.class)); + cards.add(new SetCardInfo("Underground River", 362, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); + cards.add(new SetCardInfo("Updraft", 105, Rarity.UNCOMMON, mage.cards.u.Updraft.class)); + cards.add(new SetCardInfo("Urza's Bauble", 343, Rarity.UNCOMMON, mage.cards.u.UrzasBauble.class)); + cards.add(new SetCardInfo("Veldt", 363, Rarity.RARE, mage.cards.v.Veldt.class)); + cards.add(new SetCardInfo("Venomous Breath", 273, Rarity.UNCOMMON, mage.cards.v.VenomousBreath.class)); + cards.add(new SetCardInfo("Vertigo", 222, Rarity.UNCOMMON, mage.cards.v.Vertigo.class)); + cards.add(new SetCardInfo("Vexing Arcanix", 344, Rarity.RARE, mage.cards.v.VexingArcanix.class)); + cards.add(new SetCardInfo("Vibrating Sphere", 345, Rarity.RARE, mage.cards.v.VibratingSphere.class)); + cards.add(new SetCardInfo("Walking Wall", 346, Rarity.UNCOMMON, mage.cards.w.WalkingWall.class)); + cards.add(new SetCardInfo("Wall of Lava", 223, Rarity.UNCOMMON, mage.cards.w.WallOfLava.class)); + cards.add(new SetCardInfo("Wall of Pine Needles", 274, Rarity.UNCOMMON, mage.cards.w.WallOfPineNeedles.class)); + cards.add(new SetCardInfo("Wall of Shields", 347, Rarity.UNCOMMON, mage.cards.w.WallOfShields.class)); + cards.add(new SetCardInfo("War Chariot", 348, Rarity.UNCOMMON, mage.cards.w.WarChariot.class)); + cards.add(new SetCardInfo("Warning", 55, Rarity.COMMON, mage.cards.w.Warning.class)); + cards.add(new SetCardInfo("Whalebone Glider", 349, Rarity.UNCOMMON, mage.cards.w.WhaleboneGlider.class)); + cards.add(new SetCardInfo("White Scarab", 56, Rarity.UNCOMMON, mage.cards.w.WhiteScarab.class)); + cards.add(new SetCardInfo("Whiteout", 275, Rarity.UNCOMMON, mage.cards.w.Whiteout.class)); + cards.add(new SetCardInfo("Wiitigo", 276, Rarity.RARE, mage.cards.w.Wiitigo.class)); + cards.add(new SetCardInfo("Wild Growth", 277, Rarity.COMMON, mage.cards.w.WildGrowth.class)); + cards.add(new SetCardInfo("Wind Spirit", 106, Rarity.UNCOMMON, mage.cards.w.WindSpirit.class)); + cards.add(new SetCardInfo("Wings of Aesthir", 305, Rarity.UNCOMMON, mage.cards.w.WingsOfAesthir.class)); + cards.add(new SetCardInfo("Withering Wisps", 168, Rarity.UNCOMMON, mage.cards.w.WitheringWisps.class)); + cards.add(new SetCardInfo("Woolly Mammoths", 278, Rarity.COMMON, mage.cards.w.WoollyMammoths.class)); + cards.add(new SetCardInfo("Woolly Spider", 279, Rarity.COMMON, mage.cards.w.WoollySpider.class)); + cards.add(new SetCardInfo("Word of Blasting", 224, Rarity.UNCOMMON, mage.cards.w.WordOfBlasting.class)); + cards.add(new SetCardInfo("Word of Undoing", 108, Rarity.COMMON, mage.cards.w.WordOfUndoing.class)); + cards.add(new SetCardInfo("Wrath of Marit Lage", 109, Rarity.RARE, mage.cards.w.WrathOfMaritLage.class)); + cards.add(new SetCardInfo("Yavimaya Gnats", 280, Rarity.UNCOMMON, mage.cards.y.YavimayaGnats.class)); + cards.add(new SetCardInfo("Zur's Weirding", 110, Rarity.RARE, mage.cards.z.ZursWeirding.class)); + cards.add(new SetCardInfo("Zuran Enchanter", 111, Rarity.COMMON, mage.cards.z.ZuranEnchanter.class)); + cards.add(new SetCardInfo("Zuran Orb", 350, Rarity.UNCOMMON, mage.cards.z.ZuranOrb.class)); + cards.add(new SetCardInfo("Zuran Spellcaster", 112, Rarity.COMMON, mage.cards.z.ZuranSpellcaster.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index 8cc2b5f133..573f0482df 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -152,7 +152,7 @@ public final class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Leaping Lizard", 171, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); cards.add(new SetCardInfo("Lim-Dul's High Guard", 103, Rarity.UNCOMMON, mage.cards.l.LimDulsHighGuard.class)); cards.add(new SetCardInfo("Lodestone Bauble", 213, Rarity.RARE, mage.cards.l.LodestoneBauble.class)); - cards.add(new SetCardInfo("Lost Order of Jarkeld", 24, Rarity.RARE, mage.cards.l.LostOrderOfJarkeld.class)); + cards.add(new SetCardInfo("Lost Order of Jarkeld", 24, Rarity.RARE, mage.cards.l.LostOrderOfJarkeld.class)); cards.add(new SetCardInfo("Magus of the Unseen", 53, Rarity.RARE, mage.cards.m.MagusOfTheUnseen.class)); cards.add(new SetCardInfo("Mana Crypt", 214, Rarity.RARE, mage.cards.m.ManaCrypt.class)); cards.add(new SetCardInfo("Marjhan", 54, Rarity.RARE, mage.cards.m.Marjhan.class)); @@ -195,6 +195,7 @@ public final class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Ritual of Subdual", 174, Rarity.RARE, mage.cards.r.RitualOfSubdual.class)); cards.add(new SetCardInfo("Ritual of the Machine", 109, Rarity.RARE, mage.cards.r.RitualOfTheMachine.class)); cards.add(new SetCardInfo("Roterothopter", 218, Rarity.COMMON, mage.cards.r.Roterothopter.class)); + cards.add(new SetCardInfo("Rogue Skycaptain", 149, Rarity.RARE, mage.cards.r.RogueSkycaptain.class)); cards.add(new SetCardInfo("Royal Decree", 31, Rarity.RARE, mage.cards.r.RoyalDecree.class)); cards.add(new SetCardInfo("Royal Trooper", 32, Rarity.COMMON, mage.cards.r.RoyalTrooper.class)); cards.add(new SetCardInfo("Ruins of Trokair", 234, Rarity.UNCOMMON, mage.cards.r.RuinsOfTrokair.class)); diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index cb2906dcdf..6e87d40a7c 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -279,6 +279,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Soar", 93, Rarity.COMMON, mage.cards.s.Soar.class)); cards.add(new SetCardInfo("Soul Echo", 40, Rarity.RARE, mage.cards.s.SoulEcho.class)); cards.add(new SetCardInfo("Soul Rend", 144, Rarity.UNCOMMON, mage.cards.s.SoulRend.class)); + cards.add(new SetCardInfo("Soulshriek", 145, Rarity.COMMON, mage.cards.s.Soulshriek.class)); cards.add(new SetCardInfo("Spectral Guardian", 41, Rarity.RARE, mage.cards.s.SpectralGuardian.class)); cards.add(new SetCardInfo("Spirit of the Night", 146, Rarity.RARE, mage.cards.s.SpiritOfTheNight.class)); cards.add(new SetCardInfo("Spitting Earth", 193, Rarity.COMMON, mage.cards.s.SpittingEarth.class)); @@ -314,6 +315,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Unseen Walker", 249, Rarity.UNCOMMON, mage.cards.u.UnseenWalker.class)); cards.add(new SetCardInfo("Unyaro Bee Sting", 250, Rarity.UNCOMMON, mage.cards.u.UnyaroBeeSting.class)); cards.add(new SetCardInfo("Unyaro Griffin", 44, Rarity.UNCOMMON, mage.cards.u.UnyaroGriffin.class)); + cards.add(new SetCardInfo("Urborg Panther", 150, Rarity.COMMON, mage.cards.u.UrborgPanther.class)); cards.add(new SetCardInfo("Vaporous Djinn", 101, Rarity.UNCOMMON, mage.cards.v.VaporousDjinn.class)); cards.add(new SetCardInfo("Ventifact Bottle", 323, Rarity.RARE, mage.cards.v.VentifactBottle.class)); cards.add(new SetCardInfo("Viashino Warrior", 200, Rarity.COMMON, mage.cards.v.ViashinoWarrior.class)); diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index b432e5833f..bfda5c412e 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -39,12 +39,14 @@ public final class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Growth Spurt", 61, Rarity.COMMON, mage.cards.g.GrowthSpurt.class)); cards.add(new SetCardInfo("Hungry Hungry Heifer", 63, Rarity.UNCOMMON, mage.cards.h.HungryHungryHeifer.class)); cards.add(new SetCardInfo("Incoming!", 64, Rarity.RARE, mage.cards.i.Incoming.class)); + cards.add(new SetCardInfo("Infernal Spawn of Evil", 33, Rarity.RARE, mage.cards.i.InfernalSpawnOfEvil.class)); cards.add(new SetCardInfo("Island", 85, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Jack-in-the-Mox", 75, Rarity.RARE, mage.cards.j.JackInTheMox.class)); cards.add(new SetCardInfo("Jalum Grifter", 47, Rarity.RARE, mage.cards.j.JalumGrifter.class)); cards.add(new SetCardInfo("Jumbo Imp", 34, Rarity.UNCOMMON, mage.cards.j.JumboImp.class)); cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class)); cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class)); + cards.add(new SetCardInfo("Miss Demeanor", 10, Rarity.UNCOMMON, mage.cards.m.MissDemeanor.class)); cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Once More with Feeling", 11, Rarity.RARE, mage.cards.o.OnceMoreWithFeeling.class)); cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); diff --git a/Mage.Sets/src/mage/sets/Unhinged.java b/Mage.Sets/src/mage/sets/Unhinged.java index bc8b373053..8e3e0bdeb5 100644 --- a/Mage.Sets/src/mage/sets/Unhinged.java +++ b/Mage.Sets/src/mage/sets/Unhinged.java @@ -27,6 +27,7 @@ public final class Unhinged extends ExpansionSet { cards.add(new SetCardInfo("Bloodletter", 50, Rarity.COMMON, mage.cards.b.Bloodletter.class)); cards.add(new SetCardInfo("Booster Tutor", 51, Rarity.UNCOMMON, mage.cards.b.BoosterTutor.class)); cards.add(new SetCardInfo("Double Header", 31, Rarity.COMMON, mage.cards.d.DoubleHeader.class)); + cards.add(new SetCardInfo("Elvish House Party", 94, Rarity.UNCOMMON, mage.cards.e.ElvishHouseParty.class)); cards.add(new SetCardInfo("First Come, First Served", 12, Rarity.UNCOMMON, mage.cards.f.FirstComeFirstServed.class)); cards.add(new SetCardInfo("Forest", 140, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Form of the Squirrel", 96, Rarity.RARE, mage.cards.f.FormOfTheSquirrel.class)); @@ -42,6 +43,7 @@ public final class Unhinged extends ExpansionSet { cards.add(new SetCardInfo("Old Fogey", 106, Rarity.RARE, mage.cards.o.OldFogey.class)); cards.add(new SetCardInfo("Plains", 136, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Rare-B-Gone", 119, Rarity.RARE, mage.cards.r.RareBGone.class)); + cards.add(new SetCardInfo("Rod of Spanking", 127, Rarity.UNCOMMON, mage.cards.r.RodOfSpanking.class)); cards.add(new SetCardInfo("Six-y Beast", 89, Rarity.UNCOMMON, mage.cards.s.SixyBeast.class)); cards.add(new SetCardInfo("Swamp", 138, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Symbol Status", 114, Rarity.UNCOMMON, mage.cards.s.SymbolStatus.class)); diff --git a/Mage.Sets/src/mage/sets/Unstable.java b/Mage.Sets/src/mage/sets/Unstable.java index 393aee438e..5b874239b1 100644 --- a/Mage.Sets/src/mage/sets/Unstable.java +++ b/Mage.Sets/src/mage/sets/Unstable.java @@ -42,6 +42,9 @@ public final class Unstable extends ExpansionSet { cards.add(new SetCardInfo("Dr. Julius Jumblemorph", 130, Rarity.MYTHIC, mage.cards.d.DrJuliusJumblemorph.class)); cards.add(new SetCardInfo("Enraged Killbot", "145d", Rarity.COMMON, mage.cards.e.EnragedKillbot.class)); cards.add(new SetCardInfo("Earl of Squirrel", 108, Rarity.RARE, mage.cards.e.EarlOfSquirrel.class)); + cards.add(new SetCardInfo("Everythingamajig", "147b", Rarity.UNCOMMON, mage.cards.e.EverythingamajigB.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everythingamajig", "147c", Rarity.UNCOMMON, mage.cards.e.EverythingamajigC.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everythingamajig", "147e", Rarity.UNCOMMON, mage.cards.e.EverythingamajigE.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 216, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UST_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("GO TO JAIL", 8, Rarity.COMMON, mage.cards.g.GOTOJAIL.class)); cards.add(new SetCardInfo("Garbage Elemental", "82c", Rarity.UNCOMMON, mage.cards.g.GarbageElementalC.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/Visions.java b/Mage.Sets/src/mage/sets/Visions.java index 70ae1d9df1..63ca9aeb98 100644 --- a/Mage.Sets/src/mage/sets/Visions.java +++ b/Mage.Sets/src/mage/sets/Visions.java @@ -99,6 +99,7 @@ public final class Visions extends ExpansionSet { cards.add(new SetCardInfo("Knight of the Mists", 36, Rarity.COMMON, mage.cards.k.KnightOfTheMists.class)); cards.add(new SetCardInfo("Knight of Valor", 11, Rarity.COMMON, mage.cards.k.KnightOfValor.class)); cards.add(new SetCardInfo("Kookus", 86, Rarity.RARE, mage.cards.k.Kookus.class)); + cards.add(new SetCardInfo("Kyscu Drake", 111, Rarity.RARE, mage.cards.k.KyscuDrake.class)); cards.add(new SetCardInfo("Lead-Belly Chimera", 148, Rarity.UNCOMMON, mage.cards.l.LeadBellyChimera.class)); cards.add(new SetCardInfo("Lichenthrope", 112, Rarity.RARE, mage.cards.l.Lichenthrope.class)); cards.add(new SetCardInfo("Lightning Cloud", 87, Rarity.RARE, mage.cards.l.LightningCloud.class)); @@ -124,6 +125,7 @@ public final class Visions extends ExpansionSet { cards.add(new SetCardInfo("Prosperity", 40, Rarity.UNCOMMON, mage.cards.p.Prosperity.class)); cards.add(new SetCardInfo("Python", 68, Rarity.COMMON, mage.cards.p.Python.class)); cards.add(new SetCardInfo("Quicksand", 166, Rarity.UNCOMMON, mage.cards.q.Quicksand.class)); + cards.add(new SetCardInfo("Quirion Druid", 116, Rarity.RARE, mage.cards.q.QuirionDruid.class)); cards.add(new SetCardInfo("Quirion Ranger", 117, Rarity.COMMON, mage.cards.q.QuirionRanger.class)); cards.add(new SetCardInfo("Raging Gorilla", 90, Rarity.COMMON, mage.cards.r.RagingGorilla.class)); cards.add(new SetCardInfo("Rainbow Efreet", 41, Rarity.RARE, mage.cards.r.RainbowEfreet.class)); diff --git a/Mage.Sets/src/mage/sets/WarOfTheSpark.java b/Mage.Sets/src/mage/sets/WarOfTheSpark.java index 390c5890de..c4ec025a00 100644 --- a/Mage.Sets/src/mage/sets/WarOfTheSpark.java +++ b/Mage.Sets/src/mage/sets/WarOfTheSpark.java @@ -30,14 +30,44 @@ public final class WarOfTheSpark extends ExpansionSet { cards.add(new SetCardInfo("Ajani, the Greathearted", 184, Rarity.RARE, mage.cards.a.AjaniTheGreathearted.class)); cards.add(new SetCardInfo("Angrath's Rampage", 185, Rarity.UNCOMMON, mage.cards.a.AngrathsRampage.class)); cards.add(new SetCardInfo("Angrath, Captain of Chaos", 227, Rarity.UNCOMMON, mage.cards.a.AngrathCaptainOfChaos.class)); + cards.add(new SetCardInfo("Arboreal Grazer", 149, Rarity.COMMON, mage.cards.a.ArborealGrazer.class)); cards.add(new SetCardInfo("Arlinn's Wolf", 151, Rarity.COMMON, mage.cards.a.ArlinnsWolf.class)); cards.add(new SetCardInfo("Arlinn, Voice of the Pack", 150, Rarity.UNCOMMON, mage.cards.a.ArlinnVoiceOfThePack.class)); + cards.add(new SetCardInfo("Ashiok's Skulker", 40, Rarity.COMMON, mage.cards.a.AshioksSkulker.class)); + cards.add(new SetCardInfo("Ashiok, Dream Render", 228, Rarity.UNCOMMON, mage.cards.a.AshiokDreamRender.class)); cards.add(new SetCardInfo("Augur of Bolas", 41, Rarity.UNCOMMON, mage.cards.a.AugurOfBolas.class)); + cards.add(new SetCardInfo("Aven Eternal", 42, Rarity.COMMON, mage.cards.a.AvenEternal.class)); + cards.add(new SetCardInfo("Awakening of Vitu-Ghazi", 152, Rarity.RARE, mage.cards.a.AwakeningOfVituGhazi.class)); + cards.add(new SetCardInfo("Band Together", 153, Rarity.COMMON, mage.cards.b.BandTogether.class)); cards.add(new SetCardInfo("Banehound", 77, Rarity.COMMON, mage.cards.b.Banehound.class)); + cards.add(new SetCardInfo("Battlefield Promotion", 5, Rarity.COMMON, mage.cards.b.BattlefieldPromotion.class)); + cards.add(new SetCardInfo("Bioessence Hydra", 186, Rarity.RARE, mage.cards.b.BioessenceHydra.class)); + cards.add(new SetCardInfo("Blast Zone", 244, Rarity.RARE, mage.cards.b.BlastZone.class)); + cards.add(new SetCardInfo("Bleeding Edge", 78, Rarity.UNCOMMON, mage.cards.b.BleedingEdge.class)); cards.add(new SetCardInfo("Blindblast", 114, Rarity.COMMON, mage.cards.b.Blindblast.class)); cards.add(new SetCardInfo("Bloom Hulk", 154, Rarity.COMMON, mage.cards.b.BloomHulk.class)); + cards.add(new SetCardInfo("Bolas's Citadel", 79, Rarity.RARE, mage.cards.b.BolassCitadel.class)); + cards.add(new SetCardInfo("Bolt Bend", 115, Rarity.UNCOMMON, mage.cards.b.BoltBend.class)); + cards.add(new SetCardInfo("Bond of Discipline", 6, Rarity.UNCOMMON, mage.cards.b.BondOfDiscipline.class)); + cards.add(new SetCardInfo("Bond of Flourishing", 155, Rarity.UNCOMMON, mage.cards.b.BondOfFlourishing.class)); + cards.add(new SetCardInfo("Bond of Insight", 43, Rarity.UNCOMMON, mage.cards.b.BondOfInsight.class)); + cards.add(new SetCardInfo("Bond of Passion", 116, Rarity.UNCOMMON, mage.cards.b.BondOfPassion.class)); + cards.add(new SetCardInfo("Bond of Revival", 80, Rarity.UNCOMMON, mage.cards.b.BondOfRevival.class)); cards.add(new SetCardInfo("Bulwark Giant", 7, Rarity.COMMON, mage.cards.b.BulwarkGiant.class)); cards.add(new SetCardInfo("Burning Prophet", 117, Rarity.COMMON, mage.cards.b.BurningProphet.class)); + cards.add(new SetCardInfo("Callous Dismissal", 44, Rarity.COMMON, mage.cards.c.CallousDismissal.class)); + cards.add(new SetCardInfo("Casualties of War", 187, Rarity.RARE, mage.cards.c.CasualtiesOfWar.class)); + cards.add(new SetCardInfo("Centaur Nurturer", 156, Rarity.COMMON, mage.cards.c.CentaurNurturer.class)); + cards.add(new SetCardInfo("Chainwhip Cyclops", 118, Rarity.COMMON, mage.cards.c.ChainwhipCyclops.class)); + cards.add(new SetCardInfo("Challenger Troll", 157, Rarity.UNCOMMON, mage.cards.c.ChallengerTroll.class)); + cards.add(new SetCardInfo("Chandra's Pyrohelix", 120, Rarity.COMMON, mage.cards.c.ChandrasPyrohelix.class)); + cards.add(new SetCardInfo("Chandra's Triumph", 121, Rarity.UNCOMMON, mage.cards.c.ChandrasTriumph.class)); + cards.add(new SetCardInfo("Chandra, Fire Artisan", 119, Rarity.RARE, mage.cards.c.ChandraFireArtisan.class)); + cards.add(new SetCardInfo("Charity Extractor", 81, Rarity.COMMON, mage.cards.c.CharityExtractor.class)); + cards.add(new SetCardInfo("Charmed Stray", 8, Rarity.COMMON, mage.cards.c.CharmedStray.class)); + cards.add(new SetCardInfo("Command the Dreadhorde", 82, Rarity.RARE, mage.cards.c.CommandTheDreadhorde.class)); + cards.add(new SetCardInfo("Commence the Endgame", 45, Rarity.RARE, mage.cards.c.CommenceTheEndgame.class)); + cards.add(new SetCardInfo("Contentious Plan", 46, Rarity.COMMON, mage.cards.c.ContentiousPlan.class)); cards.add(new SetCardInfo("Courage in Crisis", 158, Rarity.COMMON, mage.cards.c.CourageInCrisis.class)); cards.add(new SetCardInfo("Cruel Celebrant", 188, Rarity.UNCOMMON, mage.cards.c.CruelCelebrant.class)); cards.add(new SetCardInfo("Crush Dissent", 47, Rarity.COMMON, mage.cards.c.CrushDissent.class)); @@ -46,100 +76,225 @@ public final class WarOfTheSpark extends ExpansionSet { cards.add(new SetCardInfo("Davriel, Rogue Shadowmage", 83, Rarity.UNCOMMON, mage.cards.d.DavrielRogueShadowmage.class)); cards.add(new SetCardInfo("Deathsprout", 189, Rarity.UNCOMMON, mage.cards.d.Deathsprout.class)); cards.add(new SetCardInfo("Defiant Strike", 9, Rarity.COMMON, mage.cards.d.DefiantStrike.class)); + cards.add(new SetCardInfo("Deliver Unto Evil", 85, Rarity.RARE, mage.cards.d.DeliverUntoEvil.class)); + cards.add(new SetCardInfo("Demolish", 123, Rarity.COMMON, mage.cards.d.Demolish.class)); + cards.add(new SetCardInfo("Despark", 190, Rarity.UNCOMMON, mage.cards.d.Despark.class)); + cards.add(new SetCardInfo("Desperate Lunge", 266, Rarity.COMMON, mage.cards.d.DesperateLunge.class)); cards.add(new SetCardInfo("Devouring Hellion", 124, Rarity.UNCOMMON, mage.cards.d.DevouringHellion.class)); + cards.add(new SetCardInfo("Divine Arrow", 10, Rarity.COMMON, mage.cards.d.DivineArrow.class)); + cards.add(new SetCardInfo("Domri's Ambush", 192, Rarity.UNCOMMON, mage.cards.d.DomrisAmbush.class)); + cards.add(new SetCardInfo("Domri, Anarch of Bolas", 191, Rarity.RARE, mage.cards.d.DomriAnarchOfBolas.class)); cards.add(new SetCardInfo("Dovin's Veto", 193, Rarity.UNCOMMON, mage.cards.d.DovinsVeto.class)); + cards.add(new SetCardInfo("Dovin, Hand of Control", 229, Rarity.UNCOMMON, mage.cards.d.DovinHandOfControl.class)); cards.add(new SetCardInfo("Dreadhorde Arcanist", 125, Rarity.RARE, mage.cards.d.DreadhordeArcanist.class)); cards.add(new SetCardInfo("Dreadhorde Butcher", 194, Rarity.RARE, mage.cards.d.DreadhordeButcher.class)); cards.add(new SetCardInfo("Dreadhorde Invasion", 86, Rarity.RARE, mage.cards.d.DreadhordeInvasion.class)); + cards.add(new SetCardInfo("Dreadhorde Twins", 126, Rarity.UNCOMMON, mage.cards.d.DreadhordeTwins.class)); + cards.add(new SetCardInfo("Dreadmalkin", 87, Rarity.UNCOMMON, mage.cards.d.Dreadmalkin.class)); + cards.add(new SetCardInfo("Duskmantle Operative", 88, Rarity.COMMON, mage.cards.d.DuskmantleOperative.class)); + cards.add(new SetCardInfo("Elite Guardmage", 195, Rarity.UNCOMMON, mage.cards.e.EliteGuardmage.class)); cards.add(new SetCardInfo("Emergence Zone", 245, Rarity.UNCOMMON, mage.cards.e.EmergenceZone.class)); + cards.add(new SetCardInfo("Enforcer Griffin", 11, Rarity.COMMON, mage.cards.e.EnforcerGriffin.class)); + cards.add(new SetCardInfo("Enter the God-Eternals", 196, Rarity.RARE, mage.cards.e.EnterTheGodEternals.class)); cards.add(new SetCardInfo("Erratic Visionary", 48, Rarity.COMMON, mage.cards.e.ErraticVisionary.class)); + cards.add(new SetCardInfo("Eternal Skylord", 49, Rarity.UNCOMMON, mage.cards.e.EternalSkylord.class)); cards.add(new SetCardInfo("Eternal Taskmaster", 90, Rarity.UNCOMMON, mage.cards.e.EternalTaskmaster.class)); + cards.add(new SetCardInfo("Evolution Sage", 159, Rarity.UNCOMMON, mage.cards.e.EvolutionSage.class)); + cards.add(new SetCardInfo("Fblthp, the Lost", 50, Rarity.RARE, mage.cards.f.FblthpTheLost.class)); + cards.add(new SetCardInfo("Feather, the Redeemed", 197, Rarity.RARE, mage.cards.f.FeatherTheRedeemed.class)); + cards.add(new SetCardInfo("Finale of Devastation", 160, Rarity.MYTHIC, mage.cards.f.FinaleOfDevastation.class)); + cards.add(new SetCardInfo("Finale of Eternity", 91, Rarity.MYTHIC, mage.cards.f.FinaleOfEternity.class)); + cards.add(new SetCardInfo("Finale of Glory", 12, Rarity.MYTHIC, mage.cards.f.FinaleOfGlory.class)); + cards.add(new SetCardInfo("Finale of Promise", 127, Rarity.MYTHIC, mage.cards.f.FinaleOfPromise.class)); + cards.add(new SetCardInfo("Finale of Revelation", 51, Rarity.MYTHIC, mage.cards.f.FinaleOfRevelation.class)); + cards.add(new SetCardInfo("Firemind Vessel", 237, Rarity.UNCOMMON, mage.cards.f.FiremindVessel.class)); cards.add(new SetCardInfo("Flux Channeler", 52, Rarity.UNCOMMON, mage.cards.f.FluxChanneler.class)); + cards.add(new SetCardInfo("Forced Landing", 161, Rarity.COMMON, mage.cards.f.ForcedLanding.class)); cards.add(new SetCardInfo("Forest", 262, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 263, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 264, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gateway Plaza", 246, Rarity.COMMON, mage.cards.g.GatewayPlaza.class)); cards.add(new SetCardInfo("Giant Growth", 162, Rarity.COMMON, mage.cards.g.GiantGrowth.class)); + cards.add(new SetCardInfo("Gideon Blackblade", 13, Rarity.MYTHIC, mage.cards.g.GideonBlackblade.class)); + cards.add(new SetCardInfo("Gideon's Battle Cry", 267, Rarity.RARE, mage.cards.g.GideonsBattleCry.class)); + cards.add(new SetCardInfo("Gideon's Company", 268, Rarity.UNCOMMON, mage.cards.g.GideonsCompany.class)); + cards.add(new SetCardInfo("Gideon's Sacrifice", 14, Rarity.COMMON, mage.cards.g.GideonsSacrifice.class)); cards.add(new SetCardInfo("Gideon's Triumph", 15, Rarity.UNCOMMON, mage.cards.g.GideonsTriumph.class)); + cards.add(new SetCardInfo("Gideon, the Oathsworn", 265, Rarity.MYTHIC, mage.cards.g.GideonTheOathsworn.class)); cards.add(new SetCardInfo("Gleaming Overseer", 198, Rarity.UNCOMMON, mage.cards.g.GleamingOverseer.class)); cards.add(new SetCardInfo("Goblin Assailant", 128, Rarity.COMMON, mage.cards.g.GoblinAssailant.class)); cards.add(new SetCardInfo("Goblin Assault Team", 129, Rarity.COMMON, mage.cards.g.GoblinAssaultTeam.class)); + cards.add(new SetCardInfo("God-Eternal Bontu", 92, Rarity.MYTHIC, mage.cards.g.GodEternalBontu.class)); + cards.add(new SetCardInfo("God-Eternal Kefnet", 53, Rarity.MYTHIC, mage.cards.g.GodEternalKefnet.class)); + cards.add(new SetCardInfo("God-Eternal Oketra", 16, Rarity.MYTHIC, mage.cards.g.GodEternalOketra.class)); + cards.add(new SetCardInfo("God-Eternal Rhonas", 163, Rarity.MYTHIC, mage.cards.g.GodEternalRhonas.class)); cards.add(new SetCardInfo("God-Pharaoh's Statue", 238, Rarity.UNCOMMON, mage.cards.g.GodPharaohsStatue.class)); cards.add(new SetCardInfo("Grateful Apparition", 17, Rarity.UNCOMMON, mage.cards.g.GratefulApparition.class)); cards.add(new SetCardInfo("Grim Initiate", 130, Rarity.COMMON, mage.cards.g.GrimInitiate.class)); + cards.add(new SetCardInfo("Guild Globe", 239, Rarity.COMMON, mage.cards.g.GuildGlobe.class)); + cards.add(new SetCardInfo("Guildpact Informant", 271, Rarity.COMMON, mage.cards.g.GuildpactInformant.class)); cards.add(new SetCardInfo("Heartfire", 131, Rarity.COMMON, mage.cards.h.Heartfire.class)); + cards.add(new SetCardInfo("Heartwarming Redemption", 199, Rarity.UNCOMMON, mage.cards.h.HeartwarmingRedemption.class)); cards.add(new SetCardInfo("Herald of the Dreadhorde", 93, Rarity.COMMON, mage.cards.h.HeraldOfTheDreadhorde.class)); cards.add(new SetCardInfo("Honor the God-Pharaoh", 132, Rarity.COMMON, mage.cards.h.HonorTheGodPharaoh.class)); + cards.add(new SetCardInfo("Huatli's Raptor", 200, Rarity.UNCOMMON, mage.cards.h.HuatlisRaptor.class)); + cards.add(new SetCardInfo("Huatli, the Sun's Heart", 230, Rarity.UNCOMMON, mage.cards.h.HuatliTheSunsHeart.class)); cards.add(new SetCardInfo("Ignite the Beacon", 18, Rarity.RARE, mage.cards.i.IgniteTheBeacon.class)); + cards.add(new SetCardInfo("Ilharg, the Raze-Boar", 133, Rarity.MYTHIC, mage.cards.i.IlhargTheRazeBoar.class)); cards.add(new SetCardInfo("Interplanar Beacon", 247, Rarity.UNCOMMON, mage.cards.i.InterplanarBeacon.class)); cards.add(new SetCardInfo("Invade the City", 201, Rarity.UNCOMMON, mage.cards.i.InvadeTheCity.class)); cards.add(new SetCardInfo("Invading Manticore", 134, Rarity.COMMON, mage.cards.i.InvadingManticore.class)); cards.add(new SetCardInfo("Iron Bully", 240, Rarity.COMMON, mage.cards.i.IronBully.class)); + cards.add(new SetCardInfo("Ironclad Krovod", 19, Rarity.COMMON, mage.cards.i.IroncladKrovod.class)); cards.add(new SetCardInfo("Island", 253, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 254, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 255, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jace's Projection", 272, Rarity.UNCOMMON, mage.cards.j.JacesProjection.class)); + cards.add(new SetCardInfo("Jace's Ruse", 273, Rarity.RARE, mage.cards.j.JacesRuse.class)); + cards.add(new SetCardInfo("Jace's Triumph", 55, Rarity.UNCOMMON, mage.cards.j.JacesTriumph.class)); + cards.add(new SetCardInfo("Jace, Arcane Strategist", 270, Rarity.MYTHIC, mage.cards.j.JaceArcaneStrategist.class)); cards.add(new SetCardInfo("Jace, Wielder of Mysteries", 54, Rarity.RARE, mage.cards.j.JaceWielderOfMysteries.class)); + cards.add(new SetCardInfo("Jaya's Greeting", 136, Rarity.COMMON, mage.cards.j.JayasGreeting.class)); + cards.add(new SetCardInfo("Jaya, Venerated Firemage", 135, Rarity.UNCOMMON, mage.cards.j.JayaVeneratedFiremage.class)); cards.add(new SetCardInfo("Jiang Yanggu, Wildcrafter", 164, Rarity.UNCOMMON, mage.cards.j.JiangYangguWildcrafter.class)); cards.add(new SetCardInfo("Karn's Bastion", 248, Rarity.RARE, mage.cards.k.KarnsBastion.class)); + cards.add(new SetCardInfo("Karn, the Great Creator", 1, Rarity.RARE, mage.cards.k.KarnTheGreatCreator.class)); + cards.add(new SetCardInfo("Kasmina's Transmutation", 57, Rarity.COMMON, mage.cards.k.KasminasTransmutation.class)); + cards.add(new SetCardInfo("Kasmina, Enigmatic Mentor", 56, Rarity.UNCOMMON, mage.cards.k.KasminaEnigmaticMentor.class)); cards.add(new SetCardInfo("Kaya's Ghostform", 94, Rarity.COMMON, mage.cards.k.KayasGhostform.class)); cards.add(new SetCardInfo("Kaya, Bane of the Dead", 231, Rarity.UNCOMMON, mage.cards.k.KayaBaneOfTheDead.class)); cards.add(new SetCardInfo("Kiora's Dambreaker", 58, Rarity.COMMON, mage.cards.k.KiorasDambreaker.class)); cards.add(new SetCardInfo("Kiora, Behemoth Beckoner", 232, Rarity.UNCOMMON, mage.cards.k.KioraBehemothBeckoner.class)); + cards.add(new SetCardInfo("Kraul Stinger", 165, Rarity.COMMON, mage.cards.k.KraulStinger.class)); cards.add(new SetCardInfo("Krenko, Tin Street Kingpin", 137, Rarity.RARE, mage.cards.k.KrenkoTinStreetKingpin.class)); + cards.add(new SetCardInfo("Kronch Wrangler", 166, Rarity.COMMON, mage.cards.k.KronchWrangler.class)); + cards.add(new SetCardInfo("Law-Rune Enforcer", 20, Rarity.COMMON, mage.cards.l.LawRuneEnforcer.class)); cards.add(new SetCardInfo("Lazotep Behemoth", 95, Rarity.COMMON, mage.cards.l.LazotepBehemoth.class)); cards.add(new SetCardInfo("Lazotep Plating", 59, Rarity.UNCOMMON, mage.cards.l.LazotepPlating.class)); cards.add(new SetCardInfo("Lazotep Reaver", 96, Rarity.COMMON, mage.cards.l.LazotepReaver.class)); cards.add(new SetCardInfo("Leyline Prowler", 202, Rarity.UNCOMMON, mage.cards.l.LeylineProwler.class)); cards.add(new SetCardInfo("Liliana's Triumph", 98, Rarity.UNCOMMON, mage.cards.l.LilianasTriumph.class)); cards.add(new SetCardInfo("Liliana, Dreadhorde General", 97, Rarity.MYTHIC, mage.cards.l.LilianaDreadhordeGeneral.class)); + cards.add(new SetCardInfo("Living Twister", 203, Rarity.RARE, mage.cards.l.LivingTwister.class)); cards.add(new SetCardInfo("Loxodon Sergeant", 21, Rarity.COMMON, mage.cards.l.LoxodonSergeant.class)); cards.add(new SetCardInfo("Makeshift Battalion", 22, Rarity.COMMON, mage.cards.m.MakeshiftBattalion.class)); + cards.add(new SetCardInfo("Mana Geode", 241, Rarity.COMMON, mage.cards.m.ManaGeode.class)); + cards.add(new SetCardInfo("Martyr for the Cause", 23, Rarity.COMMON, mage.cards.m.MartyrForTheCause.class)); cards.add(new SetCardInfo("Massacre Girl", 99, Rarity.RARE, mage.cards.m.MassacreGirl.class)); cards.add(new SetCardInfo("Mayhem Devil", 204, Rarity.UNCOMMON, mage.cards.m.MayhemDevil.class)); cards.add(new SetCardInfo("Merfolk Skydiver", 205, Rarity.UNCOMMON, mage.cards.m.MerfolkSkydiver.class)); cards.add(new SetCardInfo("Mizzium Tank", 138, Rarity.RARE, mage.cards.m.MizziumTank.class)); + cards.add(new SetCardInfo("Mobilized District", 249, Rarity.RARE, mage.cards.m.MobilizedDistrict.class)); + cards.add(new SetCardInfo("Mountain", 259, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 260, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 261, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mowu, Loyal Companion", 167, Rarity.UNCOMMON, mage.cards.m.MowuLoyalCompanion.class)); cards.add(new SetCardInfo("Naga Eternal", 60, Rarity.COMMON, mage.cards.n.NagaEternal.class)); cards.add(new SetCardInfo("Nahiri's Stoneblades", 139, Rarity.COMMON, mage.cards.n.NahirisStoneblades.class)); cards.add(new SetCardInfo("Nahiri, Storm of Stone", 233, Rarity.UNCOMMON, mage.cards.n.NahiriStormOfStone.class)); + cards.add(new SetCardInfo("Narset's Reversal", 62, Rarity.RARE, mage.cards.n.NarsetsReversal.class)); + cards.add(new SetCardInfo("Narset, Parter of Veils", 61, Rarity.UNCOMMON, mage.cards.n.NarsetParterOfVeils.class)); cards.add(new SetCardInfo("Neheb, Dreadhorde Champion", 140, Rarity.RARE, mage.cards.n.NehebDreadhordeChampion.class)); + cards.add(new SetCardInfo("Neoform", 206, Rarity.UNCOMMON, mage.cards.n.Neoform.class)); + cards.add(new SetCardInfo("New Horizons", 168, Rarity.COMMON, mage.cards.n.NewHorizons.class)); + cards.add(new SetCardInfo("Nicol Bolas, Dragon-God", 207, Rarity.MYTHIC, mage.cards.n.NicolBolasDragonGod.class)); + cards.add(new SetCardInfo("Nissa's Triumph", 170, Rarity.UNCOMMON, mage.cards.n.NissasTriumph.class)); + cards.add(new SetCardInfo("Nissa, Who Shakes the World", 169, Rarity.RARE, mage.cards.n.NissaWhoShakesTheWorld.class)); + cards.add(new SetCardInfo("Niv-Mizzet Reborn", 208, Rarity.MYTHIC, mage.cards.n.NivMizzetReborn.class)); cards.add(new SetCardInfo("No Escape", 63, Rarity.COMMON, mage.cards.n.NoEscape.class)); + cards.add(new SetCardInfo("Oath of Kaya", 209, Rarity.RARE, mage.cards.o.OathOfKaya.class)); cards.add(new SetCardInfo("Ob Nixilis's Cruelty", 101, Rarity.COMMON, mage.cards.o.ObNixilissCruelty.class)); cards.add(new SetCardInfo("Ob Nixilis, the Hate-Twisted", 100, Rarity.UNCOMMON, mage.cards.o.ObNixilisTheHateTwisted.class)); + cards.add(new SetCardInfo("Orzhov Guildgate", 269, Rarity.COMMON, mage.cards.o.OrzhovGuildgate.class)); cards.add(new SetCardInfo("Paradise Druid", 171, Rarity.UNCOMMON, mage.cards.p.ParadiseDruid.class)); + cards.add(new SetCardInfo("Parhelion II", 24, Rarity.RARE, mage.cards.p.ParhelionII.class)); cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 251, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 252, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Planewide Celebration", 172, Rarity.RARE, mage.cards.p.PlanewideCelebration.class)); + cards.add(new SetCardInfo("Pledge of Unity", 210, Rarity.UNCOMMON, mage.cards.p.PledgeOfUnity.class)); cards.add(new SetCardInfo("Pollenbright Druid", 173, Rarity.COMMON, mage.cards.p.PollenbrightDruid.class)); cards.add(new SetCardInfo("Pouncing Lynx", 25, Rarity.COMMON, mage.cards.p.PouncingLynx.class)); + cards.add(new SetCardInfo("Price of Betrayal", 102, Rarity.UNCOMMON, mage.cards.p.PriceOfBetrayal.class)); cards.add(new SetCardInfo("Primordial Wurm", 174, Rarity.COMMON, mage.cards.p.PrimordialWurm.class)); + cards.add(new SetCardInfo("Prismite", 242, Rarity.COMMON, mage.cards.p.Prismite.class)); + cards.add(new SetCardInfo("Prison Realm", 26, Rarity.UNCOMMON, mage.cards.p.PrisonRealm.class)); + cards.add(new SetCardInfo("Raging Kronch", 141, Rarity.COMMON, mage.cards.r.RagingKronch.class)); + cards.add(new SetCardInfo("Ral's Outburst", 212, Rarity.UNCOMMON, mage.cards.r.RalsOutburst.class)); + cards.add(new SetCardInfo("Ral, Storm Conduit", 211, Rarity.RARE, mage.cards.r.RalStormConduit.class)); + cards.add(new SetCardInfo("Rally of Wings", 27, Rarity.UNCOMMON, mage.cards.r.RallyOfWings.class)); cards.add(new SetCardInfo("Ravnica at War", 28, Rarity.RARE, mage.cards.r.RavnicaAtWar.class)); cards.add(new SetCardInfo("Relentless Advance", 64, Rarity.COMMON, mage.cards.r.RelentlessAdvance.class)); + cards.add(new SetCardInfo("Rescuer Sphinx", 65, Rarity.UNCOMMON, mage.cards.r.RescuerSphinx.class)); + cards.add(new SetCardInfo("Return to Nature", 175, Rarity.COMMON, mage.cards.r.ReturnToNature.class)); cards.add(new SetCardInfo("Rising Populace", 29, Rarity.COMMON, mage.cards.r.RisingPopulace.class)); + cards.add(new SetCardInfo("Roalesk, Apex Hybrid", 213, Rarity.MYTHIC, mage.cards.r.RoaleskApexHybrid.class)); cards.add(new SetCardInfo("Role Reversal", 214, Rarity.RARE, mage.cards.r.RoleReversal.class)); + cards.add(new SetCardInfo("Rubblebelt Rioters", 215, Rarity.UNCOMMON, mage.cards.r.RubblebeltRioters.class)); + cards.add(new SetCardInfo("Saheeli's Silverwing", 243, Rarity.COMMON, mage.cards.s.SaheelisSilverwing.class)); + cards.add(new SetCardInfo("Saheeli, Sublime Artificer", 234, Rarity.UNCOMMON, mage.cards.s.SaheeliSublimeArtificer.class)); cards.add(new SetCardInfo("Samut's Sprint", 142, Rarity.COMMON, mage.cards.s.SamutsSprint.class)); cards.add(new SetCardInfo("Samut, Tyrant Smasher", 235, Rarity.UNCOMMON, mage.cards.s.SamutTyrantSmasher.class)); + cards.add(new SetCardInfo("Sarkhan the Masterless", 143, Rarity.RARE, mage.cards.s.SarkhanTheMasterless.class)); + cards.add(new SetCardInfo("Sarkhan's Catharsis", 144, Rarity.COMMON, mage.cards.s.SarkhansCatharsis.class)); + cards.add(new SetCardInfo("Shriekdiver", 103, Rarity.COMMON, mage.cards.s.Shriekdiver.class)); + cards.add(new SetCardInfo("Silent Submersible", 66, Rarity.RARE, mage.cards.s.SilentSubmersible.class)); + cards.add(new SetCardInfo("Simic Guildgate", 274, Rarity.COMMON, mage.cards.s.SimicGuildgate.class)); cards.add(new SetCardInfo("Single Combat", 30, Rarity.RARE, mage.cards.s.SingleCombat.class)); + cards.add(new SetCardInfo("Sky Theater Strix", 67, Rarity.COMMON, mage.cards.s.SkyTheaterStrix.class)); + cards.add(new SetCardInfo("Snarespinner", 176, Rarity.COMMON, mage.cards.s.Snarespinner.class)); + cards.add(new SetCardInfo("Solar Blaze", 216, Rarity.RARE, mage.cards.s.SolarBlaze.class)); cards.add(new SetCardInfo("Sorin's Thirst", 104, Rarity.COMMON, mage.cards.s.SorinsThirst.class)); cards.add(new SetCardInfo("Sorin, Vengeful Bloodlord", 217, Rarity.RARE, mage.cards.s.SorinVengefulBloodlord.class)); + cards.add(new SetCardInfo("Soul Diviner", 218, Rarity.RARE, mage.cards.s.SoulDiviner.class)); + cards.add(new SetCardInfo("Spark Double", 68, Rarity.RARE, mage.cards.s.SparkDouble.class)); + cards.add(new SetCardInfo("Spark Harvest", 105, Rarity.COMMON, mage.cards.s.SparkHarvest.class)); + cards.add(new SetCardInfo("Spark Reaper", 106, Rarity.COMMON, mage.cards.s.SparkReaper.class)); cards.add(new SetCardInfo("Spellgorger Weird", 145, Rarity.COMMON, mage.cards.s.SpellgorgerWeird.class)); cards.add(new SetCardInfo("Spellkeeper Weird", 69, Rarity.COMMON, mage.cards.s.SpellkeeperWeird.class)); + cards.add(new SetCardInfo("Steady Aim", 177, Rarity.COMMON, mage.cards.s.SteadyAim.class)); cards.add(new SetCardInfo("Stealth Mission", 70, Rarity.COMMON, mage.cards.s.StealthMission.class)); + cards.add(new SetCardInfo("Storm the Citadel", 178, Rarity.UNCOMMON, mage.cards.s.StormTheCitadel.class)); cards.add(new SetCardInfo("Storrev, Devkarin Lich", 219, Rarity.RARE, mage.cards.s.StorrevDevkarinLich.class)); + cards.add(new SetCardInfo("Sunblade Angel", 31, Rarity.UNCOMMON, mage.cards.s.SunbladeAngel.class)); cards.add(new SetCardInfo("Swamp", 256, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 257, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 258, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tamiyo's Epiphany", 71, Rarity.COMMON, mage.cards.t.TamiyosEpiphany.class)); + cards.add(new SetCardInfo("Tamiyo, Collector of Tales", 220, Rarity.RARE, mage.cards.t.TamiyoCollectorOfTales.class)); + cards.add(new SetCardInfo("Teferi's Time Twist", 72, Rarity.COMMON, mage.cards.t.TeferisTimeTwist.class)); cards.add(new SetCardInfo("Teferi, Time Raveler", 221, Rarity.RARE, mage.cards.t.TeferiTimeRaveler.class)); cards.add(new SetCardInfo("Tenth District Legionnaire", 222, Rarity.UNCOMMON, mage.cards.t.TenthDistrictLegionnaire.class)); cards.add(new SetCardInfo("Teyo's Lightshield", 33, Rarity.COMMON, mage.cards.t.TeyosLightshield.class)); cards.add(new SetCardInfo("Teyo, the Shieldmage", 32, Rarity.UNCOMMON, mage.cards.t.TeyoTheShieldmage.class)); cards.add(new SetCardInfo("Tezzeret, Master of the Bridge", 275, Rarity.MYTHIC, mage.cards.t.TezzeretMasterOfTheBridge.class)); + cards.add(new SetCardInfo("The Elderspell", 89, Rarity.RARE, mage.cards.t.TheElderspell.class)); cards.add(new SetCardInfo("The Wanderer", 37, Rarity.UNCOMMON, mage.cards.t.TheWanderer.class)); + cards.add(new SetCardInfo("Thunder Drake", 73, Rarity.COMMON, mage.cards.t.ThunderDrake.class)); + cards.add(new SetCardInfo("Thundering Ceratok", 179, Rarity.COMMON, mage.cards.t.ThunderingCeratok.class)); cards.add(new SetCardInfo("Tibalt's Rager", 147, Rarity.UNCOMMON, mage.cards.t.TibaltsRager.class)); cards.add(new SetCardInfo("Tibalt, Rakish Instigator", 146, Rarity.UNCOMMON, mage.cards.t.TibaltRakishInstigator.class)); cards.add(new SetCardInfo("Time Wipe", 223, Rarity.RARE, mage.cards.t.TimeWipe.class)); + cards.add(new SetCardInfo("Tithebearer Giant", 107, Rarity.COMMON, mage.cards.t.TithebearerGiant.class)); + cards.add(new SetCardInfo("Toll of the Invasion", 108, Rarity.COMMON, mage.cards.t.TollOfTheInvasion.class)); + cards.add(new SetCardInfo("Tolsimir, Friend to Wolves", 224, Rarity.RARE, mage.cards.t.TolsimirFriendToWolves.class)); + cards.add(new SetCardInfo("Tomik, Distinguished Advokist", 34, Rarity.RARE, mage.cards.t.TomikDistinguishedAdvokist.class)); + cards.add(new SetCardInfo("Topple the Statue", 35, Rarity.COMMON, mage.cards.t.ToppleTheStatue.class)); cards.add(new SetCardInfo("Totally Lost", 74, Rarity.COMMON, mage.cards.t.TotallyLost.class)); + cards.add(new SetCardInfo("Trusted Pegasus", 36, Rarity.COMMON, mage.cards.t.TrustedPegasus.class)); + cards.add(new SetCardInfo("Turret Ogre", 148, Rarity.COMMON, mage.cards.t.TurretOgre.class)); + cards.add(new SetCardInfo("Tyrant's Scorn", 225, Rarity.UNCOMMON, mage.cards.t.TyrantsScorn.class)); + cards.add(new SetCardInfo("Ugin's Conjurant", 3, Rarity.UNCOMMON, mage.cards.u.UginsConjurant.class)); + cards.add(new SetCardInfo("Ugin, the Ineffable", 2, Rarity.RARE, mage.cards.u.UginTheIneffable.class)); + cards.add(new SetCardInfo("Unlikely Aid", 109, Rarity.COMMON, mage.cards.u.UnlikelyAid.class)); + cards.add(new SetCardInfo("Vampire Opportunist", 110, Rarity.COMMON, mage.cards.v.VampireOpportunist.class)); cards.add(new SetCardInfo("Vivien's Arkbow", 181, Rarity.RARE, mage.cards.v.ViviensArkbow.class)); cards.add(new SetCardInfo("Vivien's Grizzly", 182, Rarity.COMMON, mage.cards.v.ViviensGrizzly.class)); + cards.add(new SetCardInfo("Vivien, Champion of the Wilds", 180, Rarity.RARE, mage.cards.v.VivienChampionOfTheWilds.class)); + cards.add(new SetCardInfo("Vizier of the Scorpion", 111, Rarity.UNCOMMON, mage.cards.v.VizierOfTheScorpion.class)); cards.add(new SetCardInfo("Vraska's Finisher", 112, Rarity.COMMON, mage.cards.v.VraskasFinisher.class)); cards.add(new SetCardInfo("Vraska, Swarm's Eminence", 236, Rarity.UNCOMMON, mage.cards.v.VraskaSwarmsEminence.class)); + cards.add(new SetCardInfo("Wall of Runes", 75, Rarity.COMMON, mage.cards.w.WallOfRunes.class)); cards.add(new SetCardInfo("Wanderer's Strike", 38, Rarity.COMMON, mage.cards.w.WanderersStrike.class)); cards.add(new SetCardInfo("War Screecher", 39, Rarity.COMMON, mage.cards.w.WarScreecher.class)); cards.add(new SetCardInfo("Wardscale Crocodile", 183, Rarity.COMMON, mage.cards.w.WardscaleCrocodile.class)); diff --git a/Mage.Sets/src/mage/sets/Weatherlight.java b/Mage.Sets/src/mage/sets/Weatherlight.java index e0a7dc3b9c..b8be7cb40e 100644 --- a/Mage.Sets/src/mage/sets/Weatherlight.java +++ b/Mage.Sets/src/mage/sets/Weatherlight.java @@ -55,6 +55,7 @@ public final class Weatherlight extends ExpansionSet { cards.add(new SetCardInfo("Bogardan Firefiend", 91, Rarity.COMMON, mage.cards.b.BogardanFirefiend.class)); cards.add(new SetCardInfo("Boiling Blood", 92, Rarity.COMMON, mage.cards.b.BoilingBlood.class)); cards.add(new SetCardInfo("Bone Dancer", 62, Rarity.RARE, mage.cards.b.BoneDancer.class)); + cards.add(new SetCardInfo("Bosium Strip", 147, Rarity.RARE, mage.cards.b.BosiumStrip.class)); cards.add(new SetCardInfo("Briar Shield", 121, Rarity.COMMON, mage.cards.b.BriarShield.class)); cards.add(new SetCardInfo("Bubble Matrix", 146, Rarity.RARE, mage.cards.b.BubbleMatrix.class)); cards.add(new SetCardInfo("Buried Alive", 63, Rarity.UNCOMMON, mage.cards.b.BuriedAlive.class)); @@ -159,6 +160,7 @@ public final class Weatherlight extends ExpansionSet { cards.add(new SetCardInfo("Strands of Night", 82, Rarity.UNCOMMON, mage.cards.s.StrandsOfNight.class)); cards.add(new SetCardInfo("Straw Golem", 158, Rarity.UNCOMMON, mage.cards.s.StrawGolem.class)); cards.add(new SetCardInfo("Striped Bears", 140, Rarity.COMMON, mage.cards.s.StripedBears.class)); + cards.add(new SetCardInfo("Sylvan Hierophant", 141, Rarity.UNCOMMON, mage.cards.s.SylvanHierophant.class)); cards.add(new SetCardInfo("Tariff", 28, Rarity.RARE, mage.cards.t.Tariff.class)); cards.add(new SetCardInfo("Teferi's Veil", 53, Rarity.UNCOMMON, mage.cards.t.TeferisVeil.class)); cards.add(new SetCardInfo("Tendrils of Despair", 83, Rarity.COMMON, mage.cards.t.TendrilsOfDespair.class)); diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml index cf5eee3e22..33c0ad55c7 100644 --- a/Mage.Tests/pom.xml +++ b/Mage.Tests/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-tests diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java index fd43fd2061..4d20873802 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/curses/CursesTest.java @@ -426,7 +426,7 @@ public class CursesTest extends CardTestPlayerBase { } /* - * Reported bug issue #3326 (NOTE test is failing due to bug in code) + * Reported bug issue #3326 * When {Witchbane Orb} triggers when entering the field and there IS a curse attached to you, an error message (I sadly skipped) appears and your turn is reset. This happened to me in a 4-player Commander game with {Curse of the Shallow Graves} on the field. */ diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/FeatherTheRedeemedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/FeatherTheRedeemedTest.java new file mode 100644 index 0000000000..bc353d8bd0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/FeatherTheRedeemedTest.java @@ -0,0 +1,121 @@ +package org.mage.test.cards.abilities.other; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class FeatherTheRedeemedTest extends CardTestPlayerBase { + + // Feather, the Redeemed {R}{W}{W} + /* + Whenever you cast an instant or sorcery spell that targets a creature you control, exile that card + instead of putting it into your graveyard as it resolves. If you do, return it to your hand at the beginning of the next end step. + */ + + @Test + public void test_ExileSpellWithReturnAtTheEnd() { + // cast bolt, put to exile, return to hand + addCard(Zone.BATTLEFIELD, playerA, "Feather, the Redeemed"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); + + // cast and put to exile + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Grizzly Bears"); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1); + checkExileCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", 1); + checkHandCardCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + // return to hand at the next end step + checkExileCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + checkHandCardCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_ExileSpellAndRecastWithReturnAtTheEnd() { + // cast bolt, put to exile, cast from exile, put to exile, return to hand + addCustomCardWithAbility("cast from exile", playerA, new SimpleStaticAbility( + new PlayFromNotOwnHandZoneAllEffect(StaticFilters.FILTER_CARD, Zone.EXILED, false, TargetController.ANY, Duration.WhileOnBattlefield) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Feather, the Redeemed"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 3); + + // cast and put to exile + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 3 - 1); + checkExileCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); + checkHandCardCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + // cast from exile and put to exile again + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 3 - 2); + checkExileCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); + checkHandCardCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + // return to hand at the next end step + setChoice(playerA, "At the beginning of the next end step"); // two triggeres from two cast (card's code adds two same effects on each trigger) + checkExileCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + checkHandCardCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_ExileSpellAndRecastWithoutReturn() { + // cast bolt, put to exile, cast from exile with different target, put to graveyard, not return + addCustomCardWithAbility("cast from exile", playerA, new SimpleStaticAbility( + new PlayFromNotOwnHandZoneAllEffect(StaticFilters.FILTER_CARD, Zone.EXILED, false, TargetController.ANY, Duration.WhileOnBattlefield) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Feather, the Redeemed"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 3); + + // cast and put to exile + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 3 - 1); + checkExileCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1); + checkHandCardCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + // cast from exile to target player (without trigger) and put to graveyard + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 3 - 1); // no changes + checkLife("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 3); + checkExileCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + checkHandCardCount("turn 1 recast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + // not return to hand at the next end step + checkExileCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + checkHandCardCount("turn 1 after", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 0); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/OathOfKayaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/OathOfKayaTest.java new file mode 100644 index 0000000000..cd78d60a12 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/OathOfKayaTest.java @@ -0,0 +1,80 @@ +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class OathOfKayaTest extends CardTestPlayerBase { + + @Test + public void test_AttackingPlayer() { + // Whenever an opponent attacks a planeswalker you control with one or more creatures, + // Oath of Kaya deals 2 damage to that player and you gain 2 life. + addCard(Zone.BATTLEFIELD, playerB, "Oath of Kaya", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Liliana, Dreadhorde General", 1); + + attack(1, playerA, "Grizzly Bears", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerB, "Liliana, Dreadhorde General", CounterType.LOYALTY, 6); + assertLife(playerA, 20); + assertLife(playerB, 20 - 2); + } + + @Test + public void test_AttackingPlaneswalker() { + // Whenever an opponent attacks a planeswalker you control with one or more creatures, + // Oath of Kaya deals 2 damage to that player and you gain 2 life. + addCard(Zone.BATTLEFIELD, playerB, "Oath of Kaya", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Liliana, Dreadhorde General", 1); + + attack(1, playerA, "Grizzly Bears", "Liliana, Dreadhorde General"); + attack(1, playerA, "Grizzly Bears", "Liliana, Dreadhorde General"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerB, "Liliana, Dreadhorde General", CounterType.LOYALTY, 6 - 2 * 2); + assertLife(playerA, 20 - 2); + assertLife(playerB, 20 + 2); + } + + @Test + public void test_AttackingTwoPlaneswalkers() { + // Whenever an opponent attacks a planeswalker you control with one or more creatures, + // Oath of Kaya deals 2 damage to that player and you gain 2 life. + addCard(Zone.BATTLEFIELD, playerB, "Oath of Kaya", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Liliana, Dreadhorde General", 1); + addCard(Zone.BATTLEFIELD, playerB, "Vivien, Champion of the Wilds", 1); + + attack(1, playerA, "Grizzly Bears", "Liliana, Dreadhorde General"); + attack(1, playerA, "Grizzly Bears", "Vivien, Champion of the Wilds"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerB, "Liliana, Dreadhorde General", CounterType.LOYALTY, 6 - 2); + assertCounterCount(playerB, "Vivien, Champion of the Wilds", CounterType.LOYALTY, 4 - 2); + assertLife(playerA, 20 - 2); + assertLife(playerB, 20 + 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PaupersCageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PaupersCageTest.java new file mode 100644 index 0000000000..460b433fcf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/PaupersCageTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.abilities.other; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class PaupersCageTest extends CardTestPlayerBase { + + // Paupers' Cage + // At the beginning of each opponent's upkeep, if that player has two or fewer cards in hand, + // Paupers' Cage deals 2 damage to him or her. + + @Test + public void test_TooManyCards() { + addCard(Zone.BATTLEFIELD, playerA, "Paupers' Cage", 1); + // + addCard(Zone.HAND, playerA, "Island", 5); + addCard(Zone.HAND, playerB, "Island", 5); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + } + + @Test + public void test_YouHaveFewCards() { + addCard(Zone.BATTLEFIELD, playerA, "Paupers' Cage", 1); + // + //addCard(Zone.HAND, playerA, "Island", 5); + addCard(Zone.HAND, playerB, "Island", 5); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + } + + @Test + public void test_OpponentHaveFewCards() { + addCard(Zone.BATTLEFIELD, playerA, "Paupers' Cage", 1); + // + addCard(Zone.HAND, playerA, "Island", 5); + //addCard(Zone.HAND, playerB, "Island", 5); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20 - 2); + } + + @Test + public void test_OpponentHaveFewCardsMultipleTurns() { + addCard(Zone.BATTLEFIELD, playerA, "Paupers' Cage", 1); + // + addCard(Zone.HAND, playerA, "Island", 5); + //addCard(Zone.HAND, playerB, "Island", 5); + + setStrictChooseMode(true); + setStopAt(4, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20 - 2 * 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/DidNotHaveHexproofTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/DidNotHaveHexproofTest.java new file mode 100644 index 0000000000..369faa3aec --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/DidNotHaveHexproofTest.java @@ -0,0 +1,55 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.mage.test.cards.asthough; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author drmDev + */ +public class DidNotHaveHexproofTest extends CardTestPlayerBase { + + /* + Witchbane Orb (4) + When Witchbane Orb enters the battlefield, destroy all Curses attached to you. + You have hexproof. (You can't be the target of spells or abilities your opponents control, including Aura spells.) + */ + public static final String wOrb = "Witchbane Orb"; + + /* + Detection Tower (Land) + {T}: Add Colorless. + 1, {T}: Until end of turn, your opponents and creatures your opponents control with hexproof can be the targets of spells and abilities you control + as though they didn't have hexproof. + */ + public static final String dTower = "Detection Tower"; + + @Test + public void detectionTowerAllowsTargettingPlayerWithWitchbaneOrb() { + + addCard(Zone.BATTLEFIELD, playerA, dTower); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Shock"); // {R} 2 dmg to any target + addCard(Zone.BATTLEFIELD, playerB, wOrb); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}"); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Shock", playerB); + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertTappedCount("Mountain", true, 2); + assertTapped(dTower, true); + assertGraveyardCount(playerA, "Shock", 1); + assertLife(playerB, 18); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java new file mode 100644 index 0000000000..02d57293aa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * @author JayDi85 + */ +public class CommandersCastTest extends CardTestCommander4Players { + + // Player order: A -> D -> C -> B + + @Test + public void test_CastToBattlefieldOneTime() { + addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G}, 2/2, commander + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + showCommand("commanders", 1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertCommandZoneCount(playerA, "Balduvian Bears", 0); + assertPermanentCount(playerA, "Balduvian Bears", 1); + } + + @Test + public void test_CastToBattlefieldTwoTimes() { + // Player order: A -> D -> C -> B + addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G}, 2/2, commander + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); // 2 + 4 + // + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + + // cast 1 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + // destroy commander + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Balduvian Bears"); + setChoice(playerA, "Yes"); // put to command zone again + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after destroy", 1, PhaseStep.PRECOMBAT_MAIN, playerB, playerA, "Balduvian Bears", 0); + + // cast 2 + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPermanentCount("after cast 2", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertCommandZoneCount(playerA, "Balduvian Bears", 0); + assertPermanentCount(playerA, "Balduvian Bears", 1); + assertGraveyardCount(playerB, "Lightning Bolt", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalCostModificationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalCostModificationTest.java new file mode 100644 index 0000000000..8dbe972ea3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalCostModificationTest.java @@ -0,0 +1,139 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.condition.common.NotMyTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.cost.AbilitiesCostReductionControllerEffect; +import mage.abilities.effects.common.cost.SpellsCostIncreasementAllEffect; +import mage.abilities.keyword.EquipAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class ConditionalCostModificationTest extends CardTestPlayerBase { + + // Dagger of the Worthy {2} + // Equipped creature gets +2/+0 and has afflict 1. + // Equip {2} + + @Test + public void test_NoModification() { + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Dagger of the Worthy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertTappedCount("Mountain", true, 2); + assertTappedCount("Mountain", false, 0); + } + + @Test + public void test_ModificationNormal() { + addCustomCardWithAbility("mod", playerA, new SimpleStaticAbility(new AbilitiesCostReductionControllerEffect(EquipAbility.class, "equip"))); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Dagger of the Worthy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertTappedCount("Mountain", true, 1); + assertTappedCount("Mountain", false, 1); + } + + @Test + public void test_ModificationConditionalActive() { + addCustomCardWithAbility("mod", playerA, new SimpleStaticAbility( + new ConditionalCostModificationEffect( + new AbilitiesCostReductionControllerEffect(EquipAbility.class, "equip"), + MyTurnCondition.instance, + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Dagger of the Worthy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertTappedCount("Mountain", true, 1); + assertTappedCount("Mountain", false, 1); + } + + @Test + public void test_ModificationConditionalNotActive() { + addCustomCardWithAbility("mod", playerA, new SimpleStaticAbility( + new ConditionalCostModificationEffect( + new AbilitiesCostReductionControllerEffect(EquipAbility.class, "equip"), + NotMyTurnCondition.instance, + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Dagger of the Worthy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertTappedCount("Mountain", true, 2); + assertTappedCount("Mountain", false, 0); + } + + @Test + public void test_ModificationConditionalNotActiveWithOtherEffect() { + addCustomCardWithAbility("mod", playerA, new SimpleStaticAbility( + new ConditionalCostModificationEffect( + new AbilitiesCostReductionControllerEffect(EquipAbility.class, "equip"), + NotMyTurnCondition.instance, + new SpellsCostIncreasementAllEffect(1), + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Dagger of the Worthy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); // no mod, 2 cost + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); // +1 for spell, 2 cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertTappedCount("Mountain", true, 4); + assertTappedCount("Mountain", false, 0); + assertLife(playerB, 20 - 3); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java new file mode 100644 index 0000000000..42c8b0bb6f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java @@ -0,0 +1,137 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.condition.common.NotMyTurnCondition; +import mage.abilities.decorator.ConditionalPreventionEffect; +import mage.abilities.effects.common.PreventAllDamageToAllEffect; +import mage.abilities.effects.common.PreventAllDamageToPlayersEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class ConditionalPreventionTest extends CardTestPlayerBase { + + // conditional effects go to layered, but there are prevention effects list too + + @Test + public void test_NotPreventDamage() { + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertHandCount(playerA, "Lightning Bolt", 0); + } + + @Test + public void test_PreventDamageNormal() { + addCustomCardWithAbility("prevent", playerA, new SimpleStaticAbility(new PreventAllDamageToAllEffect(Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT))); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 1); + assertHandCount(playerA, "Lightning Bolt", 0); + } + + @Test + public void test_PreventDamageConditionalActive() { + addCustomCardWithAbility("prevent", playerA, new SimpleStaticAbility( + new ConditionalPreventionEffect( + new PreventAllDamageToAllEffect(Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT), + MyTurnCondition.instance, + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 1); + assertHandCount(playerA, "Lightning Bolt", 0); + } + + @Test + public void test_PreventDamageConditionalNotActive() { + addCustomCardWithAbility("prevent", playerA, new SimpleStaticAbility( + new ConditionalPreventionEffect( + new PreventAllDamageToAllEffect(Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT), + NotMyTurnCondition.instance, + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertHandCount(playerA, "Lightning Bolt", 0); + } + + @Test + public void test_PreventDamageConditionalNotActiveWithOtherEffect() { + addCustomCardWithAbility("prevent", playerA, new SimpleStaticAbility( + new ConditionalPreventionEffect( + new PreventAllDamageToAllEffect(Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT), + new PreventAllDamageToPlayersEffect(Duration.WhileOnBattlefield, false), + NotMyTurnCondition.instance, + "" + ) + )); + + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); // will prevent + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); // will not prevent + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 0); // not prevented, dies + assertLife(playerA, 20); // prevented, no damage + assertHandCount(playerA, "Lightning Bolt", 0); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java new file mode 100644 index 0000000000..b25fd55e46 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java @@ -0,0 +1,156 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBaseWithRangeAll; + +/** + * @author JayDi85 + */ +public class EndOfTurnMultiOpponentsTest extends CardTestMultiPlayerBaseWithRangeAll { + + String cardBear2 = EndOfTurnOneOpponentTest.cardBear2; + + @Test + public void test_EndOfTurnMulti() { + // Player order: A -> D -> C -> B + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.EndOfTurn))); + + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 1, playerA, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 2, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 3, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 4, playerB, true, null); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 5, playerA, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 6, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 7, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 8, playerB, true, null); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 9, playerA, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 10, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 11, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.EndOfTurn effect", 12, playerB, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerD, cardBear2); + attack(3, playerC, cardBear2); + attack(4, playerB, cardBear2); + // + attack(5, playerA, cardBear2); + attack(6, playerD, cardBear2); + attack(7, playerC, cardBear2); + attack(8, playerB, cardBear2); + // + attack(9, playerA, cardBear2); + attack(10, playerD, cardBear2); + attack(11, playerC, cardBear2); + attack(12, playerB, cardBear2); + + setStopAt(12, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_UntilYourNextTurnMulti() { + // Player order: A -> D -> C -> B + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilYourNextTurn))); + + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 2, playerD, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 3, playerC, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 4, playerB, true, PhaseStep.END_TURN); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 5, playerA, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 6, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 7, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 8, playerB, true, null); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 9, playerA, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 10, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 11, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 12, playerB, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerD, cardBear2); + attack(3, playerC, cardBear2); + attack(4, playerB, cardBear2); + // + attack(5, playerA, cardBear2); + attack(6, playerD, cardBear2); + attack(7, playerC, cardBear2); + attack(8, playerB, cardBear2); + // + attack(9, playerA, cardBear2); + attack(10, playerD, cardBear2); + attack(11, playerC, cardBear2); + attack(12, playerB, cardBear2); + + setStopAt(12, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_UntilEndOfYourNextTurnMulti() { + // Player order: A -> D -> C -> B + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilEndOfYourNextTurn))); + + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 2, playerD, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 3, playerC, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 4, playerB, true, PhaseStep.END_TURN); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 5, playerA, true, PhaseStep.END_TURN); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 6, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 7, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 8, playerB, true, null); + // + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 9, playerA, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 10, playerD, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 11, playerC, true, null); + EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilEndOfYourNextTurn effect", 12, playerB, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerD, cardBear2); + attack(3, playerC, cardBear2); + attack(4, playerB, cardBear2); + // + attack(5, playerA, cardBear2); + attack(6, playerD, cardBear2); + attack(7, playerC, cardBear2); + attack(8, playerB, cardBear2); + // + attack(9, playerA, cardBear2); + attack(10, playerD, cardBear2); + attack(11, playerC, cardBear2); + attack(12, playerB, cardBear2); + + setStopAt(12, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnOneOpponentTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnOneOpponentTest.java new file mode 100644 index 0000000000..04afbed368 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnOneOpponentTest.java @@ -0,0 +1,125 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; +import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; + +/** + * @author JayDi85 + */ +public class EndOfTurnOneOpponentTest extends CardTestPlayerBase { + + public static String cardBear2 = "Balduvian Bears"; // 2/2 + + public static void prepareStepChecks(CardTestPlayerAPIImpl testEngine, String testName, int turnNum, TestPlayer player, + boolean willBeBattle, PhaseStep mustExistUntilStep) { + for (PhaseStep step : PhaseStep.values()) { + // skip auto-steps without priority/checks + switch (step) { + case UNTAP: + case DRAW: + case UPKEEP: + case FIRST_COMBAT_DAMAGE: + case CLEANUP: + continue; // auto-skip steps without priority + case PRECOMBAT_MAIN: + case POSTCOMBAT_MAIN: + case END_TURN: + break; // always use + case BEGIN_COMBAT: + case DECLARE_ATTACKERS: + case DECLARE_BLOCKERS: + case COMBAT_DAMAGE: + case END_COMBAT: + if (!willBeBattle) continue; // combat skip + break; + default: + throw new IllegalStateException("Unknown phase step " + step); + } + + int permP = 2; + int permT = 2; + String existsStr = "must NOT EXISTS"; + if (mustExistUntilStep != null && step.getIndex() <= mustExistUntilStep.getIndex()) { + permP++; + permT++; + existsStr = "must EXISTS"; + } + + testEngine.checkPT(testName + " " + existsStr + " on turn " + turnNum + " - " + step.toString() + " for " + player.getName(), + turnNum, step, player, cardBear2, permP, permT); + } + } + + @Test + public void test_EndOfTurnSingle() { + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.EndOfTurn))); + prepareStepChecks(this, "Duration.EndOfTurn effect", 1, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.EndOfTurn effect", 2, playerA, true, null); + prepareStepChecks(this, "Duration.EndOfTurn effect", 3, playerA, true, null); + prepareStepChecks(this, "Duration.EndOfTurn effect", 4, playerA, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerB, cardBear2); + attack(3, playerA, cardBear2); + attack(4, playerB, cardBear2); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_UntilYourNextTurnSingle() { + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilYourNextTurn))); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 2, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 3, playerA, true, null); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 4, playerA, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerB, cardBear2); + attack(3, playerA, cardBear2); + attack(4, playerB, cardBear2); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_UntilEndOfYourNextTurnSingle() { + addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilEndOfYourNextTurn))); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 2, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 3, playerA, true, PhaseStep.END_TURN); + prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 4, playerA, true, null); + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + + attack(1, playerA, cardBear2); + attack(2, playerB, cardBear2); + attack(3, playerA, cardBear2); + attack(4, playerB, cardBear2); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonBlackbladeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonBlackbladeTest.java new file mode 100644 index 0000000000..9f1baf27a7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonBlackbladeTest.java @@ -0,0 +1,39 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class GideonBlackbladeTest extends CardTestPlayerBase { + + // Gideon Blackblade L4 + // As long as it's your turn, Gideon Blackblade is a 4/4 Human Soldier creature with indestructible that's still a planeswalker. + // Prevent all damage that would be dealt to Gideon Blackblade during your turn. + + @Test + public void test_PreventDamageToGideonOnYourTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Gideon Blackblade"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); + + checkPT("turn 1 before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gideon Blackblade", 4, 4); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Lightning Bolt", "Gideon Blackblade"); + checkPT("turn 1 after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Blackblade", 4, 4); + checkPermanentCounters("turn 1 after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Blackblade", CounterType.LOYALTY, 4); + + checkPT("turn 2 before", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Gideon Blackblade", 0, 0); + castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Lightning Bolt", "Gideon Blackblade"); + checkPT("turn 2 after", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Blackblade", 0, 0); + checkPermanentCounters("turn 2 after", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Blackblade", CounterType.LOYALTY, 4 - 3); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonJuraAndRowanKenrithNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonJuraAndRowanKenrithNextTurnTest.java new file mode 100644 index 0000000000..3cdd9a3afe --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GideonJuraAndRowanKenrithNextTurnTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class GideonJuraAndRowanKenrithNextTurnTest extends CardTestPlayerBase { + + @Test + public void test_SingleOpponentMustAttackGideonJura() { + // +2: During target opponent's next turn, creatures that player controls attack Gideon Jura if able. + addCard(Zone.BATTLEFIELD, playerA, "Gideon Jura"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2:", playerB); + + checkPermanentCounters("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Jura", CounterType.LOYALTY, 6 + 2); + checkPermanentCounters("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Jura", CounterType.LOYALTY, 6 + 2 - 2); + checkPermanentCounters("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Jura", CounterType.LOYALTY, 6 + 2 - 2); + checkPermanentCounters("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerA, "Gideon Jura", CounterType.LOYALTY, 6 + 2 - 2); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_SingleOpponentMustAttackRowanKenrith() { + // +2: During target player's next turn, each creature that player controls attacks if able. + addCard(Zone.BATTLEFIELD, playerA, "Rowan Kenrith"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2:", playerB); + addTarget(playerB, playerA); + + checkLife("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); + checkLife("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + checkLife("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + checkLife("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MartyrsOfKorlisTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MartyrsOfKorlisTest.java new file mode 100644 index 0000000000..6cb3214d4f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MartyrsOfKorlisTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class MartyrsOfKorlisTest extends CardTestPlayerBase { + + // Martyrs of Korlis 1/6 + // As long as Martyrs of Korlis is untapped, all damage that would be dealt to you by artifacts is dealt to Martyrs of Korlis instead. + + @Test + public void test_PreventDamageToGideonOnYourTurn() { + addCard(Zone.BATTLEFIELD, playerB, "Martyrs of Korlis"); + addCard(Zone.BATTLEFIELD, playerA, "Alloy Myr"); // 2/2 + + // with redirect + checkDamage("turn 1 before", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Martyrs of Korlis", 0); + checkLife("turn 1 before", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 20); + attack(1, playerA, "Alloy Myr", playerB); + checkDamage("turn 1 after", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Martyrs of Korlis", 2); + checkLife("turn 1 after", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, 20); + + attack(2, playerB, "Martyrs of Korlis", playerA); + + // without redirect + checkDamage("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerB, "Martyrs of Korlis", 0); + checkLife("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerB, 20); + attack(3, playerA, "Alloy Myr", playerB); + checkDamage("turn 3 after", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Martyrs of Korlis", 0); + checkLife("turn 3 after", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, 20 - 2); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java new file mode 100644 index 0000000000..3abbaaae75 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java @@ -0,0 +1,56 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class OracleEnVecNextTurnTest extends CardTestPlayerBase { + + @Test + public void test_SingleOpponentMustAttack() { + // {T}: Target opponent chooses any number of creatures they control. During that player’s next turn, the chosen + // creatures attack if able, and other creatures can’t attack. At the beginning of that turn’s end step, + // destroy each of the chosen creatures that didn’t attack this turn. Activate this ability only during your turn. + addCard(Zone.BATTLEFIELD, playerA, "Oracle en-Vec"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Angelic Wall", 1); // wall, can't attack + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1); // 2/2 + + // 1 - activate + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target opponent", playerB); + setChoice(playerB, "Balduvian Bears^Angelic Wall"); + // + checkLife("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Wall", 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + + // 2 - attack and destroy at the end + checkLife("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + checkPermanentCount("turn 2a", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 2a", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Wall", 1); + checkPermanentCount("turn 2a", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + + // 3 - nothing + // after destroy at the end + checkLife("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Wall", 0); + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + + // 4 - nothing + checkLife("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Angelic Wall", 0); + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PlayerLeavesGameTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PlayerLeavesGameTest.java new file mode 100644 index 0000000000..fafe53a58b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PlayerLeavesGameTest.java @@ -0,0 +1,195 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBaseWithRangeAll; + +/** + * @author JayDi85 + */ +public class PlayerLeavesGameTest extends CardTestMultiPlayerBaseWithRangeAll { + + /* + 800.4a When a player leaves the game, all objects (see rule 109) owned by that player leave the game and any effects + which give that player control of any objects or players end. Then, if that player controlled any objects on the stack + not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player, + those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game. + If the player who left the game had priority at the time he or she left, priority passes to the next player in turn + order who’s still in the game. + */ + + String cardBear2 = "Balduvian Bears"; // 2/2 + + @Test + public void test_PlayerLeaveGame() { + // Player order: A -> D -> C -> B + + // B must checks A for online status + checkPlayerInGame("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPlayerInGame("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPlayerInGame("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + + concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); + + checkPlayerInGame("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false); + checkPlayerInGame("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_PlayerLeaveGameWithOwnPermanent() { + // Player order: A -> D -> C -> B + + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + + // B must checks A for online status + checkPlayerInGame("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + checkPlayerInGame("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + checkPlayerInGame("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount("turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + + concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); + + checkPlayerInGame("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false); + checkPermanentCount("turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, cardBear2, 0); + checkPlayerInGame("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false); + checkPermanentCount("turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 0); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + private void prepareAndRunLeaveGameWithLongEffectTest(Duration duration) { + // Player order: A -> D -> C -> B + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + addCustomCardWithAbility("effect", playerA, new SimpleStaticAbility(new BoostAllEffect(1, 1, duration))); + + // B must checks A for online status + + // 1 + checkPlayerInGame(duration.toString() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount(duration.name() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + checkPT(duration.name() + " - turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + // 2 + checkPlayerInGame(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + checkPT(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + // 3 + checkPlayerInGame(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true); + checkPermanentCount(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 1); + checkPT(duration.name() + " - turn 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + // + concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); + // + checkPlayerInGame(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, false); + checkPermanentCount(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, playerA, cardBear2, 0); + checkPT(duration.name() + " - turn 3 after", 3, PhaseStep.END_TURN, playerD, cardBear2, 2, 2); + // 4 + checkPlayerInGame(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, false); + checkPermanentCount(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, cardBear2, 0); + checkPT(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 2, 2); + + setStopAt(4, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_PlayerLeaveGameWithOwnPermanentAndCustomEffect() { + prepareAndRunLeaveGameWithLongEffectTest(Duration.Custom); + } + + @Test + public void test_PlayerLeaveGameWithOwnPermanentAndWhileOnBattlefieldEffect() { + prepareAndRunLeaveGameWithLongEffectTest(Duration.WhileOnBattlefield); + } + + @Test + public void test_PlayerLeaveGameWithOwnPermanentAndEndOfGameEffect() { + prepareAndRunLeaveGameWithLongEffectTest(Duration.EndOfGame); + } + + @Test + public void test_PlayerLeaveGameWithOwnPermanentAndUntilSourceLeavesBattlefielEffect() { + prepareAndRunLeaveGameWithLongEffectTest(Duration.UntilSourceLeavesBattlefield); + } + + @Test + public void test_EndOfTurnMultiLeave() { + // Player order: A -> D -> C -> B + addCustomCardWithAbility("boost", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.EndOfTurn))); + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + + // player A leaves game on turn 1 - postcombat + // end of turn effect must continue until end of turn + concede(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + checkPT("turn 1", 1, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPT("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPT("turn 1", 1, PhaseStep.END_TURN, playerD, cardBear2, 3, 3); + checkPT("turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 2, 2); + + setStopAt(2, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + + private void prepareAndRunUntilYourTurnLeaveTest(Duration duration) { + // Player order: A -> D -> C -> B + addCustomCardWithAbility("boost", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, duration))); + addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1); + addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1); + + // 800.4k When a player leaves the game, any continuous effects with durations that last until + // that player’s next turn or until a specific point in that turn will last until that turn would have begun. + // They neither expire immediately nor last indefinitely. + + // player A leaves game on turn 1 - postcombat + // until your next turn effect must continue until START of your possible next turn + concede(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + checkPT(duration.name() + " - turn 1 pre", 1, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPermanentCount(duration.name() + " - perm A must exists before leave", 1, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, "boost", 1); + checkPT(duration.name() + " - turn 1 post", 1, PhaseStep.POSTCOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPermanentCount(duration.name() + " - perm A must removed after leave", 1, PhaseStep.POSTCOMBAT_MAIN, playerD, playerA, "boost", 0); + checkPT(duration.name() + " - turn 1 end", 1, PhaseStep.END_TURN, playerD, cardBear2, 3, 3); + checkPT(duration.name() + " - turn 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPT(duration.name() + " - turn 3", 3, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPT(duration.name() + " - turn 4", 4, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 3, 3); + checkPT(duration.name() + " - turn 5 (possible A)", 5, PhaseStep.PRECOMBAT_MAIN, playerD, cardBear2, 2, 2); + + setStopAt(5, PhaseStep.CLEANUP); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_UntilYourNextTurnMultiLeave() { + prepareAndRunUntilYourTurnLeaveTest(Duration.UntilYourNextTurn); + } + + @Test + public void test_UntilEndOfYourNextTurnMultiLeave() { + prepareAndRunUntilYourTurnLeaveTest(Duration.UntilEndOfYourNextTurn); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ShinenOfLifesRoarTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ShinenOfLifesRoarTest.java new file mode 100644 index 0000000000..a3b7a67b9a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ShinenOfLifesRoarTest.java @@ -0,0 +1,28 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class ShinenOfLifesRoarTest extends CardTestPlayerBase { + + @Test + public void test_SingleOpponentMustBlock() { + // All creatures able to block Shinen of Life’s Roar do so. + addCard(Zone.BATTLEFIELD, playerA, "Shinen of Life's Roar"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + + attack(1, playerA, "Shinen of Life's Roar", playerB); + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Shinen of Life's Roar", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TroveOfTemptationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TroveOfTemptationTest.java new file mode 100644 index 0000000000..a980bbd46e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TroveOfTemptationTest.java @@ -0,0 +1,27 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class TroveOfTemptationTest extends CardTestPlayerBase { + + @Test + @Ignore // TODO: 2019-04-28 - improve and uncomment test after computer player can process playerMustBeAttackedIfAble restriction + public void test_SingleOpponentMustAttack() { + // Each opponent must attack you or a planeswalker you control with at least one creature each combat if able. + addCard(Zone.BATTLEFIELD, playerA, "Trove of Temptation"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1); // 2/2 + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java new file mode 100644 index 0000000000..f23d35e852 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/VraskaTheUnseenNextTurnTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class VraskaTheUnseenNextTurnTest extends CardTestPlayerBase { + + @Test + public void test_SingleOpponentMustAttack() { + // +1: Until your next turn, whenever a creature deals combat damage to Vraska the Unseen, destroy that creature. + addCard(Zone.BATTLEFIELD, playerA, "Vraska the Unseen"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + + // 1 - activate + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + checkPermanentCounters("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 3); + + // 2 - attack and destroy + attack(2, playerB, "Balduvian Bears", "Vraska the Unseen"); + checkPermanentCounters("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1 - 2); + checkPermanentCount("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 3 - 1); + + // 3 - nothing + checkPermanentCounters("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1 - 2); + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 3 - 1); + + // 4 - attack and DO NOT destroy + checkPermanentCounters("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerA, "Vraska the Unseen", CounterType.LOYALTY, 5 + 1 - 2 * 2); + attack(4, playerB, "Balduvian Bears", "Vraska the Unseen"); + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 3 - 1); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/WallOfDustNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/WallOfDustNextTurnTest.java new file mode 100644 index 0000000000..612b356406 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/WallOfDustNextTurnTest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.AttacksIfAbleAllEffect; +import mage.constants.Duration; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.watchers.common.AttackedThisTurnWatcher; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class WallOfDustNextTurnTest extends CardTestPlayerBase { + + @Test + public void test_SingleOpponentMustAttack() { + // Whenever Wall of Dust blocks a creature, that creature can't attack during its controller's next turn. + addCard(Zone.BATTLEFIELD, playerA, "Wall of Dust"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 1); // 2/2 + // + Ability ability = new SimpleStaticAbility(new AttacksIfAbleAllEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, Duration.EndOfGame)); + ability.addWatcher(new AttackedThisTurnWatcher()); + addCustomCardWithAbility("all attacks", playerA, ability); + + // 1 - nothing + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); + + // 2 - auto-attack B -> A, 1 attacked, 1 blocked (by wall) + block(2, playerA, "Wall of Dust", "Balduvian Bears"); + checkPermanentCount("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + + // 3 - nothing + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2); + + // 4 - auto-attack, B -> A, 1 attacked, 1 can't attacked (by wall's abilitiy during next turn) + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2 * 2); + + // 5 - nothing + checkPermanentCount("turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2 * 2); + + // 6 - auto-attack, B -> A, 2 attacked + checkPermanentCount("turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkPermanentCount("turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ashcoat Bear", 1); + checkLife("turn 6", 6, PhaseStep.POSTCOMBAT_MAIN, playerA, 20 - 2 * 2 - 2 * 2); + + setStopAt(6, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildCopyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildCopyTest.java new file mode 100644 index 0000000000..bbe31c5770 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildCopyTest.java @@ -0,0 +1,127 @@ +package org.mage.test.cards.copy; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class EssenceOfTheWildCopyTest extends CardTestPlayerBase { + + // Essence of the Wild {3}{G}{G}{G} + // Creatures you control enter the battlefield as a copy of Essence of the Wild. + + private Permanent findCopyPermanent(Game game, int num) { + int currentCopyNumber = 1; + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (perm.isCopy()) { + if (currentCopyNumber == num) { + return perm; + } + currentCopyNumber++; + } + } + Assert.fail("copy " + num + " must exist"); + return null; + } + + private Permanent findOriginPermanent(Game game, String permName) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (!perm.isCopy() && perm.getName().equals(permName)) { + return perm; + } + } + Assert.fail("can't find origin"); + return null; + } + + @Test + public void test_CopyCreature() { + // essence copy creature + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 2); + + Permanent copy = findCopyPermanent(currentGame, 1); + Assert.assertEquals("must have 6 p/t", 6, copy.getPower().getValue()); + Assert.assertEquals("must have 6 p/t", 6, copy.getToughness().getValue()); + } + + @Test + @Ignore // TODO: enable and fix random failes with replace effects + public void test_CopyCreatureByCopied() { + // essence copy to creature 1 -> creature 1 copy to creature + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 3); + + Permanent copy1 = findCopyPermanent(currentGame, 1); + Permanent copy2 = findCopyPermanent(currentGame, 2); + Assert.assertFalse("copy must be diffent", copy1.equals(copy2)); + Assert.assertEquals("copy 1 must have 6 p/t", 6, copy1.getPower().getValue()); + Assert.assertEquals("copy 1 must have 6 p/t", 6, copy1.getToughness().getValue()); + Assert.assertEquals("copy 2 must have 6 p/t", 6, copy2.getPower().getValue()); + Assert.assertEquals("copy 2 must have 6 p/t", 6, copy2.getToughness().getValue()); + } + + @Test + public void test_CopyCreatureWithContinuousEffect() { + // essence with -1/-1 must copy creature with normal p/t + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Liliana, Death Wielder", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + + // apply -1/-1 effect (+2: Put a -1/-1 counter on up to one target creature.) + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "+2:", "Essence of the Wild"); + + // copy + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 2); + + Permanent origin = findOriginPermanent(currentGame, "Essence of the Wild"); + Permanent copy = findCopyPermanent(currentGame, 1); + Assert.assertEquals("origin must have 5 p/t", 6 - 1, origin.getPower().getValue()); + Assert.assertEquals("origin must have 5 p/t", 6 - 1, origin.getToughness().getValue()); + Assert.assertEquals("copy must have 6 p/t", 6, copy.getPower().getValue()); + Assert.assertEquals("copy must have 6 p/t", 6, copy.getToughness().getValue()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildTest.java new file mode 100644 index 0000000000..19d3072581 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildTest.java @@ -0,0 +1,127 @@ +package org.mage.test.cards.copy; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class EssenceOfTheWildTest extends CardTestPlayerBase { + + // Essence of the Wild {3}{G}{G}{G} + // Creatures you control enter the battlefield as a copy of Essence of the Wild. + + private Permanent findCopyPermanent(Game game, int num) { + int currentCopyNumber = 1; + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (perm.isCopy()) { + if (currentCopyNumber == num) { + return perm; + } + currentCopyNumber++; + } + } + Assert.fail("copy " + num + " must exist"); + return null; + } + + private Permanent findOriginPermanent(Game game, String permName) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (!perm.isCopy() && perm.getName().equals(permName)) { + return perm; + } + } + Assert.fail("can't find origin"); + return null; + } + + @Test + public void test_CopyCreature() { + // essence copy creature + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 2); + + Permanent copy = findCopyPermanent(currentGame, 1); + Assert.assertEquals("must have 6 p/t", 6, copy.getPower().getValue()); + Assert.assertEquals("must have 6 p/t", 6, copy.getToughness().getValue()); + } + + @Test + @Ignore // TODO: enable for copy effect tests and random replacement effects fix + public void test_CopyCreatureByCopied() { + // essence copy to creature 1 -> creature 1 copy to creature + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 3); + + Permanent copy1 = findCopyPermanent(currentGame, 1); + Permanent copy2 = findCopyPermanent(currentGame, 2); + Assert.assertFalse("copy must be diffent", copy1.equals(copy2)); + Assert.assertEquals("copy 1 must have 6 p/t", 6, copy1.getPower().getValue()); + Assert.assertEquals("copy 1 must have 6 p/t", 6, copy1.getToughness().getValue()); + Assert.assertEquals("copy 2 must have 6 p/t", 6, copy2.getPower().getValue()); + Assert.assertEquals("copy 2 must have 6 p/t", 6, copy2.getToughness().getValue()); + } + + @Test + public void test_CopyCreatureWithContinuusEffect() { + // essence with -1/-1 must copy creature with normal p/t + addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Liliana, Death Wielder", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + + // apply -1/-1 effect (+2: Put a -1/-1 counter on up to one target creature.) + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "+2:", "Essence of the Wild"); + + // copy + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Essence of the Wild", 2); + + Permanent origin = findOriginPermanent(currentGame, "Essence of the Wild"); + Permanent copy = findCopyPermanent(currentGame, 1); + Assert.assertEquals("origin must have 5 p/t", 6 - 1, origin.getPower().getValue()); + Assert.assertEquals("origin must have 5 p/t", 6 - 1, origin.getToughness().getValue()); + Assert.assertEquals("copy must have 6 p/t", 6, copy.getPower().getValue()); + Assert.assertEquals("copy must have 6 p/t", 6, copy.getToughness().getValue()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildtest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildtest.java deleted file mode 100644 index b389a74beb..0000000000 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EssenceOfTheWildtest.java +++ /dev/null @@ -1,77 +0,0 @@ - -package org.mage.test.cards.copy; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.filter.Filter; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class EssenceOfTheWildtest extends CardTestPlayerBase { - - /** - * Essence of the Wild does not seem to correctly apply its copy effect to - * your creatures. Upon entering the battlefield the other creatures had a - * small symbol at the top right of their card to view the original card - - * however, both 'sides' showed only the same, original card. - * Power/Toughness and other abilities were also still those of the original - * cards. - * - * Note: This was observed in a deck controlled by the computer when testing - * other decks. - * - */ - @Test - public void testCreatureCast() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Creatures you control enter the battlefield as a copy of Essence of the Wild. - addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild"); // 6/6 - addCard(Zone.HAND, playerA, "Silvercoat Lion"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPermanentCount(playerA, "Essence of the Wild", 2); - assertPowerToughness(playerA, "Essence of the Wild", 6, 6, Filter.ComparisonScope.All); - - } - - /** - * I control Essence of the Wild and Back from the Brink on the battlefield, - * and start using Back from the Brink on the creatures in my graveyard. The - * creature tokens don't enter the battlefield as copies of Essence of the - * Wild. - * - * Since it's an unusual situation, I checked around if there's something in - * the rules that would prevent this combo from working. Found this link and - * they confirmed that it should work, the tokens should come into play as - * 6/6s. - */ - @Test - public void testWithBackFromTheBrink() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Creatures you control enter the battlefield as a copy of Essence of the Wild. - addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild"); // 6/6 - // Exile a creature card from your graveyard and pay its mana cost: Create a tokenonto the battlefield that's a copy of that card. Activate this ability only any time you could cast a sorcery. - addCard(Zone.BATTLEFIELD, playerA, "Back from the Brink"); // Enchantment - - addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile a creature card"); - setChoice(playerA, "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertExileCount("Silvercoat Lion", 1); - assertPermanentCount(playerA, "Essence of the Wild", 2); - assertPowerToughness(playerA, "Essence of the Wild", 6, 6, Filter.ComparisonScope.All); - - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java new file mode 100644 index 0000000000..b11ff19852 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java @@ -0,0 +1,157 @@ +package org.mage.test.cards.copy; + +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class SparkDoubleTest extends CardTestPlayerBase { + + private Permanent findDoubleSparkPermanent(Game game) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (perm.isCopy()) { + return perm; + } + } + Assert.fail("spark must exist"); + return null; + } + + @Test + public void test_CopyCreatureAndGetOneCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // 2/2, fly, vig + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Abbey Griffin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Abbey Griffin", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 counter", 1, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + // + Assert.assertEquals("must copy p/t", 3, spark.getPower().getValue()); + Assert.assertEquals("must copy p/t", 3, spark.getToughness().getValue()); + Assert.assertTrue("must copy ability", spark.getAbilities().contains(VigilanceAbility.getInstance())); + } + + @Test + public void test_CopyPlaneswalkerWithoutLegendaryWithOneCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Ajani, the Greathearted", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Ajani, the Greathearted"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Ajani, the Greathearted", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 5 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + } + + @Test + public void test_CopyCreatureAndGetDoubleCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // 2/2, fly, vig + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Abbey Griffin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Abbey Griffin", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 2 counter", 2, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } + + @Test + public void test_CopyPlaneswalkerWithCreatureActivated() { + addCard(Zone.BATTLEFIELD, playerA, "Gideon, Ally of Zendikar", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + // activate creature ability + checkType("planeswalker not creature", 1, PhaseStep.UPKEEP, playerA, "Gideon, Ally of Zendikar", CardType.CREATURE, false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + checkType("planeswalker is creature", 1, PhaseStep.BEGIN_COMBAT, playerA, "Gideon, Ally of Zendikar", CardType.CREATURE, true); + + // copy + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Gideon, Ally of Zendikar"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Gideon, Ally of Zendikar", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 4 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + Assert.assertEquals("must not add creature counter", 0, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } + + @Test + @Ignore // TODO: enabled after Blood Moon type changing effect will be fixed + public void test_CopyPlaneswalkerWithCreatureTypeChangedEffect() { + addCard(Zone.BATTLEFIELD, playerA, "Gideon Blackblade", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + // Gideon Blackblade is creature on your turn (by type changing effect) + checkType("planeswalker is creature", 1, PhaseStep.UPKEEP, playerA, "Gideon Blackblade", CardType.CREATURE, true); + + // copy + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Gideon Blackblade"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Gideon Blackblade", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 4 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + Assert.assertEquals("must add 1 creature counter", 1, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java index 1d428a30a0..173bb38a70 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DoublingSeasonTest.java @@ -205,7 +205,7 @@ public class DoublingSeasonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA,"+1: Draw a card, then discard a card at random."); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Draw a card, then discard a card at random."); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -216,4 +216,38 @@ public class DoublingSeasonTest extends CardTestPlayerBase { //Should not be doubled assertCounterCount("Tibalt, the Fiend-Blooded", CounterType.LOYALTY, 3); } + + /** + * +1 cost is not affected by double, but replace event like Pir, Imaginative Rascal will be affected + * https://github.com/magefree/mage/issues/5802 + */ + @Test + public void testPlaneswalkerWithoutReplacementEffect() { + //addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Fire Artisan"); + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, 4 + 1); + } + + @Test + public void testPlaneswalkerWithReplacementEffect() { + addCard(Zone.BATTLEFIELD, playerA, "Chandra, Fire Artisan"); + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season"); // x2 counters + addCard(Zone.BATTLEFIELD, playerA, "Pir, Imaginative Rascal"); // +1 counter + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertCounterCount(playerA, "Chandra, Fire Artisan", CounterType.LOYALTY, 4 + (1 + 1) * 2); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index 85bb3f87a3..d7a8819c87 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -225,4 +225,77 @@ public class BlockRequirementTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Dimensional Infiltrator", 1); assertGraveyardCount(playerB, "Llanowar Elves", 1); } + + /* + Reported bug: Challenger Troll on field not enforcing block restrictions + */ + @Test + public void testChallengerTrollTryBlockWithMany() { + /* + Challenger Troll {4}{G} - 6/5 + Creature — Troll + Each creature you control with power 4 or greater can’t be blocked by more than one creature. + */ + String cTroll = "Challenger Troll"; + + String bSable = "Bronze Sable"; // {2} 2/1 + String hGiant = "Hill Giant"; // {3}{R} 3/3 + + addCard(Zone.BATTLEFIELD, playerA, cTroll); + addCard(Zone.BATTLEFIELD, playerB, bSable); + addCard(Zone.BATTLEFIELD, playerB, hGiant); + + attack(1, playerA, cTroll); + + // only 1 should be able to block it since Troll >=4 power block restriction + block(1, playerB, bSable, cTroll); + block(1, playerB, hGiant, cTroll); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + + try { + execute(); + fail("Expected exception not thrown"); + } catch (UnsupportedOperationException e) { + assertEquals("Challenger Troll is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); + } + } + + /* + Reported bug: Challenger Troll on field not enforcing block restrictions + */ + @Test + public void testChallengerTrollAndOtherFourPowerCreaturesBlocks() { + /* + Challenger Troll {4}{G} - 6/5 + Creature — Troll + Each creature you control with power 4 or greater can’t be blocked by more than one creature. + */ + String cTroll = "Challenger Troll"; + String bHulk = "Bloom Hulk"; // {3}{G} 4/4 ETB: proliferate + + String bSable = "Bronze Sable"; // {2} 2/1 + String hGiant = "Hill Giant"; // {3}{R} 3/3 + + addCard(Zone.BATTLEFIELD, playerA, cTroll); + addCard(Zone.BATTLEFIELD, playerA, bHulk); + addCard(Zone.BATTLEFIELD, playerB, bSable); + addCard(Zone.BATTLEFIELD, playerB, hGiant); + + attack(1, playerA, cTroll); + attack(1, playerA, bHulk); + + // only 1 should be able to block Bloom Hulk since >=4 power and Troll on field + block(1, playerB, bSable, bHulk); + block(1, playerB, hGiant, bHulk); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + + try { + execute(); + fail("Expected exception not thrown"); + } catch (UnsupportedOperationException e) { + assertEquals("Bloom Hulk is blocked by 2 creature(s). It can only be blocked by 1 or less.", e.getMessage()); + } + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/war/KioraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/war/KioraTest.java new file mode 100644 index 0000000000..18ba5479c2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/war/KioraTest.java @@ -0,0 +1,50 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.single.war; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author escplan9 + */ +public class KioraTest extends CardTestPlayerBase { + + /* + {2} {U/G} + 7 loyalty + Whenever a creature with power 4 or greater enters the battlefield, draw a card. + [-1]: Untap target permanent. + */ + public final String kiora = "Kiora, Behemoth Beckoner"; + + + @Test + public void kioraUntapLand() { + + addCard(Zone.BATTLEFIELD, playerA, kiora); + addCard(Zone.BATTLEFIELD, playerA, "Bronze Sable"); // {2} 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.HAND, playerA, "Giant Growth"); + + // cast a spell to tap the only land + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Bronze Sable"); + + // untap that only land + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-1: Untap target permanent", "Forest"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Giant Growth", 1); + assertHandCount(playerA, 0); + assertPowerToughness(playerA, "Bronze Sable", 5, 4); // giant growthed 2/1 + 3/3 = 5/4 + assertCounterCount(playerA, kiora, CounterType.LOYALTY, 6); + assertTapped("Forest", false); // Kiora's untap + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/TeferiMageOfZhalfirTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/TeferiMageOfZhalfirTest.java index 03d175d197..ea39296364 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/TeferiMageOfZhalfirTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/TeferiMageOfZhalfirTest.java @@ -1,8 +1,6 @@ - package org.mage.test.commander.duel; -import java.io.FileNotFoundException; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.Game; @@ -11,8 +9,9 @@ import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestCommanderDuelBase; +import java.io.FileNotFoundException; + /** - * * @author LevelX2 */ @@ -21,11 +20,11 @@ public class TeferiMageOfZhalfirTest extends CardTestCommanderDuelBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { setDecknamePlayerA("CommanderDuel_UW.dck"); // Commander = Daxos of Meletis - return super.createNewGameAndPlayers(); + return super.createNewGameAndPlayers(); } - + @Test - public void castCommanderWithFlash() { + public void castCommanderWithFlash() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); @@ -36,11 +35,13 @@ public class TeferiMageOfZhalfirTest extends CardTestCommanderDuelBase { execute(); assertPermanentCount(playerA, "Daxos of Meletis", 1); - + assertAllCommandsUsed(); } - + @Test public void testCommanderDamage() { + setLife(playerA, 20); + setLife(playerB, 20); addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); // Enchant creature @@ -49,24 +50,30 @@ public class TeferiMageOfZhalfirTest extends CardTestCommanderDuelBase { addCard(Zone.HAND, playerA, "Angelic Destiny"); addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir"); - + // Daxos of Meletis can't be blocked by creatures with power 3 or greater. - // Whenever Daxos of Meletis deals combat damage to a player, exile the top card of that player's library. You gain life equal to that card's converted mana cost. Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it. + // Whenever Daxos of Meletis deals combat damage to a player, exile the top card of that player's library. + // You gain life equal to that card's converted mana cost. Until end of turn, you may cast that card + // and you may spend mana as though it were mana of any color to cast it. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Daxos of Meletis"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angelic Destiny","Daxos of Meletis"); - + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angelic Destiny", "Daxos of Meletis"); + attack(3, playerA, "Daxos of Meletis"); attack(5, playerA, "Daxos of Meletis"); attack(7, playerA, "Daxos of Meletis"); attack(9, playerA, "Daxos of Meletis"); - + checkPT("before lost", 9, PhaseStep.PRECOMBAT_MAIN, playerA, "Daxos of Meletis", 6, 6); + + setStrictChooseMode(true); setStopAt(9, PhaseStep.POSTCOMBAT_MAIN); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Daxos of Meletis", 1); - assertPowerToughness(playerA, "Daxos of Meletis", 6, 6); - + assertPowerToughness(playerA, "Daxos of Meletis", 6, 6); // no effects removes after game over -- users and tests can get last game state with all affected effects + Assert.assertEquals("Player A has won because of commander damage", true, playerA.hasWon()); - Assert.assertEquals("Player A has lost because of commander damage", true, playerB.hasLost()); - } + Assert.assertEquals("Player B has lost because of commander damage", true, playerB.hasLost()); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java index 930f37f2a0..2a5b9445d0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/SimpleMageClient.java @@ -1,6 +1,5 @@ package org.mage.test.load; -import java.util.UUID; import mage.interfaces.MageClient; import mage.interfaces.callback.ClientCallback; import mage.remote.Session; @@ -8,6 +7,8 @@ import mage.utils.MageVersion; import mage.view.GameView; import org.apache.log4j.Logger; +import java.util.UUID; + /** * For tests only * @@ -38,7 +39,7 @@ public class SimpleMageClient implements MageClient { } @Override - public void disconnected(boolean errorCall) { + public void disconnected(boolean askToReconnect) { // do nothing } @@ -62,11 +63,11 @@ public class SimpleMageClient implements MageClient { } public void setSession(Session session) { - ((LoadCallbackClient) callbackClient).setSession(session); + callbackClient.setSession(session); } public boolean isGameOver() { - return ((LoadCallbackClient) callbackClient).isGameOver(); + return callbackClient.isGameOver(); } public void setConcede(boolean needToConcede) { diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java index 711d9792b7..7f6dd82569 100644 --- a/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java @@ -1,41 +1,21 @@ - package org.mage.test.mulligan; -import mage.MageItem; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.Modes; -import mage.abilities.TriggeredAbility; -import mage.abilities.costs.VariableCost; -import mage.abilities.costs.mana.ManaCost; -import mage.cards.Card; import mage.cards.CardSetInfo; -import mage.cards.Cards; import mage.cards.basiclands.Forest; import mage.cards.decks.Deck; -import mage.choices.Choice; -import mage.constants.Outcome; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.GameOptions; import mage.game.TwoPlayerDuel; -import mage.game.combat.CombatGroup; -import mage.game.draft.Draft; -import mage.game.match.Match; import mage.game.mulligan.Mulligan; import mage.game.mulligan.MulliganType; -import mage.game.permanent.Permanent; -import mage.game.tournament.Tournament; -import mage.players.Player; -import mage.players.PlayerImpl; -import mage.target.Target; -import mage.target.TargetAmount; -import mage.target.TargetCard; -import mage.target.TargetPlayer; +import mage.players.StubPlayer; import org.apache.log4j.Logger; -import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkArgument; @@ -43,7 +23,6 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableSet; -import static java.util.stream.Collectors.toList; import static mage.constants.MultiplayerAttackOption.LEFT; import static mage.constants.RangeOfInfluence.ONE; import static mage.constants.Rarity.LAND; @@ -167,7 +146,8 @@ public class MulliganTestBase { return deck; } - interface Step {} + interface Step { + } interface MulliganStep extends Step { boolean mulligan(); @@ -242,192 +222,4 @@ public class MulliganTestBase { } - static class StubPlayer extends PlayerImpl implements Player { - - public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { - if (target instanceof TargetPlayer) { - for (Player player : game.getPlayers().values()) { - if (player.getId().equals(getId()) && target.canTarget(getId(), game)) { - target.add(player.getId(), game); - return true; - } - } - } - return false; - } - - public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game) { - cards.getCards(game).stream().map(MageItem::getId).forEach(cardId -> target.add(cardId, game)); - return true; - } - - @Override - public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { - if ("cards to PUT on the BOTTOM of your library (Discard for Mulligan)".equals(target.getFilter().getMessage())) { - chooseDiscardBottom(game, target.getMinNumberOfTargets(), cards.getCards(game) - .stream().map(MageItem::getId).collect(toList())).forEach(cardId -> target.add(cardId, game)); - } else { - UUID cardId = getOnlyElement(cards.getCards(game)).getId(); - if (chooseScry(game, cardId)) { - target.add(cardId, game); - return true; - } - } - return false; - } - - public List chooseDiscardBottom(Game game, int count, List cardIds) { - return cardIds.subList(0, count); - } - - public boolean chooseScry(Game game, UUID cardId) { - return false; - } - - @Override - public void shuffleLibrary(Ability source, Game game) { - - } - - public StubPlayer(String name, RangeOfInfluence range) { - super(name, range); - } - - @Override - public void abort() { - - } - - @Override - public void skip() { - - } - - @Override - public Player copy() { - return null; - } - - @Override - public boolean priority(Game game) { - return false; - } - - @Override - public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { - return false; - } - - @Override - public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { - return false; - } - - @Override - public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { - return false; - } - - @Override - public boolean chooseMulligan(Game game) { - return false; - } - - @Override - public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { - return false; - } - - @Override - public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { - return false; - } - - @Override - public boolean choose(Outcome outcome, Choice choice, Game game) { - return false; - } - - @Override - public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { - return false; - } - - @Override - public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { - return false; - } - - @Override - public int announceXMana(int min, int max, String message, Game game, Ability ability) { - return 0; - } - - @Override - public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { - return 0; - } - - @Override - public int chooseReplacementEffect(Map abilityMap, Game game) { - return 0; - } - - @Override - public TriggeredAbility chooseTriggeredAbility(List abilities, Game game) { - return null; - } - - @Override - public Mode chooseMode(Modes modes, Ability source, Game game) { - return null; - } - - @Override - public void selectAttackers(Game game, UUID attackingPlayerId) { - - } - - @Override - public void selectBlockers(Game game, UUID defendingPlayerId) { - - } - - @Override - public UUID chooseAttackerOrder(List attacker, Game game) { - return null; - } - - @Override - public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, List blockerOrder, Game game) { - return null; - } - - @Override - public void assignDamage(int damage, List targets, String singleTargetName, UUID sourceId, Game game) { - - } - - @Override - public int getAmount(int min, int max, String message, Game game) { - return 0; - } - - @Override - public void sideboard(Match match, Deck deck) { - - } - - @Override - public void construct(Tournament tournament, Deck deck) { - - } - - @Override - public void pickCard(List cards, Deck deck, Draft draft) { - - } - - } - } \ No newline at end of file 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 d139d183aa..e0e3b0006b 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 @@ -15,10 +15,12 @@ import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.abilities.mana.ManaOptions; import mage.cards.Card; import mage.cards.Cards; +import mage.cards.CardsImpl; import mage.cards.decks.Deck; import mage.choices.Choice; import mage.constants.*; import mage.counters.Counter; +import mage.counters.CounterType; import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; @@ -618,6 +620,13 @@ public class TestPlayer implements Player { wasProccessed = true; } + // check damage: card name, damage + if (params[0].equals(CHECK_COMMAND_DAMAGE) && params.length == 3) { + assertDamage(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); + actions.remove(action); + wasProccessed = true; + } + // check life: life if (params[0].equals(CHECK_COMMAND_LIFE) && params.length == 2) { assertLife(action, game, computerPlayer, Integer.parseInt(params[1])); @@ -625,6 +634,13 @@ public class TestPlayer implements Player { wasProccessed = true; } + // check player in game: target player, must be in game + if (params[0].equals(CHECK_COMMAND_PLAYER_IN_GAME) && params.length == 3) { + assertPlayerInGame(action, game, game.getPlayer(UUID.fromString(params[1])), Boolean.parseBoolean(params[2])); + actions.remove(action); + wasProccessed = true; + } + // check ability: card name, ability class, must have if (params[0].equals(CHECK_COMMAND_ABILITY) && params.length == 4) { assertAbility(action, game, computerPlayer, params[1], params[2], Boolean.parseBoolean(params[3])); @@ -632,9 +648,16 @@ public class TestPlayer implements Player { wasProccessed = true; } - // check battlefield count: card name, count - if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 3) { - assertPermanentCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2])); + // check battlefield count: target player, card name, count + if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 4) { + assertPermanentCount(action, game, game.getPlayer(UUID.fromString(params[1])), params[2], Integer.parseInt(params[3])); + actions.remove(action); + wasProccessed = true; + } + + // check permanent counters: card name, counter type, count + if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNTERS) && params.length == 4) { + assertPermanentCounters(action, game, computerPlayer, params[1], CounterType.findByName(params[2]), Integer.parseInt(params[3])); actions.remove(action); wasProccessed = true; } @@ -667,6 +690,13 @@ public class TestPlayer implements Player { wasProccessed = true; } + // check type: card name, type, must have + if (params[0].equals(CHECK_COMMAND_TYPE) && params.length == 4) { + assertType(action, game, computerPlayer, params[1], CardType.fromString(params[2]), Boolean.parseBoolean(params[3])); + actions.remove(action); + wasProccessed = true; + } + // check subtype: card name, subtype, must have if (params[0].equals(CHECK_COMMAND_SUBTYPE) && params.length == 4) { assertSubType(action, game, computerPlayer, params[1], SubType.fromString(params[2]), Boolean.parseBoolean(params[3])); @@ -717,6 +747,16 @@ public class TestPlayer implements Player { wasProccessed = true; } + // show command + if (params[0].equals(SHOW_COMMAND_COMMAND) && params.length == 1) { + printStart(action.getActionName()); + CardsImpl cards = new CardsImpl(computerPlayer.getCommandersIds()); + printCards(cards.getCards(game)); + printEnd(); + actions.remove(action); + wasProccessed = true; + } + // show battlefield if (params[0].equals(SHOW_COMMAND_BATTLEFIELD) && params.length == 1) { printStart(action.getActionName()); @@ -829,8 +869,8 @@ public class TestPlayer implements Player { List data = cards.stream() .map(c -> (c.getIdName() - + " - " + c.getPower().getValue() - + "/" + c.getToughness().getValue() + + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue() + + (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "") + ", " + (c.isTapped() ? "Tapped" : "Untapped") + (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName()) )) @@ -908,11 +948,29 @@ public class TestPlayer implements Player { Toughness, perm.getToughness().getValue()); } + private void assertDamage(PlayerAction action, Game game, Player player, String permanentName, int damage) { + Permanent perm = findPermanentWithAssert(action, game, player, permanentName); + + Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " have wrong damage: " + perm.getDamage() + " <> " + damage, damage, perm.getDamage()); + } + private void assertLife(PlayerAction action, Game game, Player player, int Life) { Assert.assertEquals(action.getActionName() + " - " + player.getName() + " have wrong life: " + player.getLife() + " <> " + Life, Life, player.getLife()); } + private void assertPlayerInGame(PlayerAction action, Game game, Player targetPlayer, boolean mustBeInGame) { + Assert.assertNotNull("Can't find target player", targetPlayer); + + if (targetPlayer.isInGame() && !mustBeInGame) { + Assert.fail(action.getActionName() + " - player " + targetPlayer.getName() + " must NOT be in game"); + } + + if (!targetPlayer.isInGame() && mustBeInGame) { + Assert.fail(action.getActionName() + " - player " + targetPlayer.getName() + " must be in game"); + } + } + private void assertAbility(PlayerAction action, Game game, Player player, String permanentName, String abilityClass, boolean mustHave) { Permanent perm = findPermanentWithAssert(action, game, player, permanentName); @@ -942,6 +1000,17 @@ public class TestPlayer implements Player { Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must exists in " + count + " instances", count, foundedCount); } + private void assertPermanentCounters(PlayerAction action, Game game, Player player, String permanentName, CounterType counterType, int count) { + int foundedCount = 0; + for (Permanent perm : game.getBattlefield().getAllPermanents()) { + if (perm.getName().equals(permanentName) && perm.getControllerId().equals(player.getId())) { + foundedCount = perm.getCounters(game).getCount(counterType); + } + } + + Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must have " + count + " " + counterType.toString(), count, foundedCount); + } + private void assertExileCount(PlayerAction action, Game game, Player player, String permanentName, int count) { int foundedCount = 0; for (Card card : game.getExile().getAllCards(game)) { @@ -994,6 +1063,25 @@ public class TestPlayer implements Player { } } + private void assertType(PlayerAction action, Game game, Player player, String permanentName, CardType type, boolean mustHave) { + + Permanent perm = findPermanentWithAssert(action, game, player, permanentName); + + boolean founded = false; + for (CardType ct : perm.getCardType()) { + if (ct.equals(type)) { + founded = true; + break; + } + } + + if (mustHave) { + Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must have type " + type, true, founded); + } else { + Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must have not type " + type, false, founded); + } + } + private void assertSubType(PlayerAction action, Game game, Player player, String permanentName, SubType subType, boolean mustHave) { Permanent perm = findPermanentWithAssert(action, game, player, permanentName); @@ -1025,11 +1113,8 @@ public class TestPlayer implements Player { } Player itemPlayer = game.getPlayer(objectId); - if (itemPlayer != null) { - return itemPlayer; - } + return itemPlayer; - return null; } private void assertAliasZone(PlayerAction action, Game game, TestPlayer player, String aliasName, Zone needZone, boolean mustHave) { @@ -1128,10 +1213,12 @@ public class TestPlayer implements Player { public void selectAttackers(Game game, UUID attackingPlayerId) { // Loop through players and validate can attack/block this turn UUID defenderId = null; - //List + boolean mustAttackByAction = false; + boolean madeAttackByAction = false; for (Iterator it = actions.iterator(); it.hasNext(); ) { PlayerAction action = it.next(); if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) { + mustAttackByAction = true; String command = action.getAction(); command = command.substring(command.indexOf("attack:") + 7); String[] groups = command.split("\\$"); @@ -1175,9 +1262,13 @@ public class TestPlayer implements Player { if (attacker != null && attacker.canAttack(defenderId, game)) { computerPlayer.declareAttacker(attacker.getId(), defenderId, game, false); it.remove(); + madeAttackByAction = true; } } + } + if (mustAttackByAction && !madeAttackByAction) { + this.chooseStrictModeFailed(game, "select attackers must use attack command but don't"); } } @@ -1189,10 +1280,12 @@ public class TestPlayer implements Player { @Override public void selectBlockers(Game game, UUID defendingPlayerId) { + List tempActions = new ArrayList<>(actions); + UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); // Map of Blocker reference -> list of creatures blocked Map> blockedCreaturesByCreature = new HashMap<>(); - for (PlayerAction action : actions) { + for (PlayerAction action : tempActions) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) { String command = action.getAction(); command = command.substring(command.indexOf("block:") + 6); @@ -1203,6 +1296,7 @@ public class TestPlayer implements Player { Permanent blocker = findPermanent(new FilterControlledPermanent(), blockerName, computerPlayer.getId(), game); if (canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) { computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); + actions.remove(action); } else { throw new UnsupportedOperationException(blockerName + " cannot block " + attackerName + " it is already blocking the maximum amount of creatures."); } @@ -1318,13 +1412,18 @@ public class TestPlayer implements Player { @Override public int chooseReplacementEffect(Map rEffects, Game game) { + if (rEffects.size() <= 1) { + return 0; + } if (!choices.isEmpty()) { for (String choice : choices) { - for (int index = 0; index < rEffects.size(); index++) { - if (choice.equals(rEffects.get(Integer.toString(index)))) { + int index = 0; + for (Map.Entry entry : rEffects.entrySet()) { + if (entry.getValue().startsWith(choice)) { choices.remove(choice); return index; } + index++; } } // TODO: enable fail checks and fix tests @@ -2634,23 +2733,23 @@ public class TestPlayer implements Player { } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game) { - return computerPlayer.searchLibrary(target, game); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game) { + return computerPlayer.searchLibrary(target, source, game); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, boolean triggerEvents) { - return computerPlayer.searchLibrary(target, game, triggerEvents); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, boolean triggerEvents) { + return computerPlayer.searchLibrary(target, source, game, triggerEvents); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId) { - return computerPlayer.searchLibrary(target, game, targetPlayerId); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId) { + return computerPlayer.searchLibrary(target, source, game, targetPlayerId); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents) { - return computerPlayer.searchLibrary(target, game, targetPlayerId, triggerEvents); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents) { + return computerPlayer.searchLibrary(target, source, game, targetPlayerId, triggerEvents); } @Override @@ -2838,8 +2937,8 @@ public class TestPlayer implements Player { } @Override - public boolean lookAtFaceDownCard(Card card, Game game) { - return computerPlayer.lookAtFaceDownCard(card, game); + public boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate) { + return computerPlayer.lookAtFaceDownCard(card, game, abilitiesToActivate); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander4Players.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander4Players.java new file mode 100644 index 0000000000..a122945927 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander4Players.java @@ -0,0 +1,28 @@ +package org.mage.test.serverside.base; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.CommanderFreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; +import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; + +import java.io.FileNotFoundException; + +/** + * @author JayDi85 + */ +public abstract class CardTestCommander4Players extends CardTestPlayerAPIImpl { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java index 804e28fef9..e2ea6d4ad6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java @@ -1,6 +1,5 @@ package org.mage.test.serverside.base; -import java.io.FileNotFoundException; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.FreeForAll; @@ -9,13 +8,14 @@ import mage.game.GameException; import mage.game.mulligan.VancouverMulligan; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; +import java.io.FileNotFoundException; + /** * Base class for testing single cards and effects in multiplayer game. For PvP * games { * - * @see CardTestPlayerBase} - * * @author magenoxx_at_gmail.com + * @see CardTestPlayerBase} */ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl { @@ -29,5 +29,4 @@ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl { playerD = createPlayer(game, playerD, "PlayerD"); return game; } - } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBaseWithRangeAll.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBaseWithRangeAll.java new file mode 100644 index 0000000000..abf2e9059f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBaseWithRangeAll.java @@ -0,0 +1,28 @@ +package org.mage.test.serverside.base; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; +import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; + +import java.io.FileNotFoundException; + +/** + * @author JayDi85 + */ +public abstract class CardTestMultiPlayerBaseWithRangeAll extends CardTestPlayerAPIImpl { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } +} 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 25bf051210..45eda5e7fe 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 @@ -1,12 +1,15 @@ package org.mage.test.serverside.base; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; import mage.cards.Card; +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.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.match.MatchType; import mage.game.permanent.PermanentCard; @@ -33,7 +36,7 @@ import java.util.regex.Pattern; /** * Base class for all tests. * - * @author ayratn + * @author ayratn, JayDi85 */ public abstract class MageTestPlayerBase { @@ -49,6 +52,7 @@ public abstract class MageTestPlayerBase { protected Map> battlefieldCards = new HashMap<>(); protected Map> graveyardCards = new HashMap<>(); protected Map> libraryCards = new HashMap<>(); + protected Map> commandCards = new HashMap<>(); protected Map> commands = new HashMap<>(); @@ -208,6 +212,9 @@ public abstract class MageTestPlayerBase { } 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)) { @@ -277,27 +284,36 @@ public abstract class MageTestPlayerBase { if (graveyardCards.containsKey(player)) { return graveyardCards.get(player); } - List grave = new ArrayList<>(); - graveyardCards.put(player, grave); - return grave; + List res = new ArrayList<>(); + graveyardCards.put(player, res); + return res; } protected List getLibraryCards(TestPlayer player) { if (libraryCards.containsKey(player)) { return libraryCards.get(player); } - List library = new ArrayList<>(); - libraryCards.put(player, library); - return library; + List res = new ArrayList<>(); + libraryCards.put(player, res); + return res; + } + + protected List getCommandCards(TestPlayer player) { + if (commandCards.containsKey(player)) { + return commandCards.get(player); + } + List res = new ArrayList<>(); + commandCards.put(player, res); + return res; } protected List getBattlefieldCards(TestPlayer player) { if (battlefieldCards.containsKey(player)) { return battlefieldCards.get(player); } - List battlefield = new ArrayList<>(); - battlefieldCards.put(player, battlefield); - return battlefield; + List res = new ArrayList<>(); + battlefieldCards.put(player, res); + return res; } protected Map getCommands(TestPlayer player) { @@ -333,10 +349,63 @@ public abstract class MageTestPlayerBase { return new TestPlayer(new TestComputerPlayer(name, rangeOfInfluence)); } - public void setStrictChooseMode(boolean enable) { + protected void setStrictChooseMode(boolean enable) { if (playerA != null) playerA.setChooseStrictMode(enable); if (playerB != null) playerB.setChooseStrictMode(enable); if (playerC != null) playerC.setChooseStrictMode(enable); if (playerD != null) playerD.setChooseStrictMode(enable); } + + protected void addCustomCardWithAbility(String customName, TestPlayer controllerPlayer, Ability ability) { + // add custom card with selected ability to battlefield + CustomTestCard.clearCustomAbilities(customName); + CustomTestCard.addCustomAbility(customName, ability); + CardSetInfo testSet = new CardSetInfo(customName, "custom", "123", Rarity.COMMON); + PermanentCard card = new PermanentCard(new CustomTestCard(controllerPlayer.getId(), testSet), controllerPlayer.getId(), currentGame); + getBattlefieldCards(controllerPlayer).add(card); + } +} + +// custom card with global abilities list to init (can contains abilities per card name) +class CustomTestCard extends CardImpl { + + static private Map> abilitiesList = new HashMap<>(); // card name -> abilities + + static protected void addCustomAbility(String cardName, Ability ability) { + if (!abilitiesList.containsKey(cardName)) { + abilitiesList.put(cardName, new AbilitiesImpl<>()); + } + Abilities oldAbilities = abilitiesList.get(cardName); + oldAbilities.add(ability); + } + + static protected void clearCustomAbilities(String cardName) { + abilitiesList.remove(cardName); + } + + static public void clearCustomAbilities() { + abilitiesList.clear(); + } + + + public CustomTestCard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + // load dynamic abilities by card name + Abilities extraAbitilies = abilitiesList.get(setInfo.getName()); + if (extraAbitilies != null) { + for (Ability ability : extraAbitilies) { + this.addAbility(ability.copy()); + } + } + } + + private CustomTestCard(final CustomTestCard card) { + super(card); + } + + @Override + public CustomTestCard copy() { + return new CustomTestCard(this); + } } 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 9e47af7659..8b66bcc20a 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 @@ -52,20 +52,25 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement // TODO: add target player param to commands public static final String CHECK_COMMAND_PT = "PT"; + public static final String CHECK_COMMAND_DAMAGE = "DAMAGE"; public static final String CHECK_COMMAND_LIFE = "LIFE"; public static final String CHECK_COMMAND_ABILITY = "ABILITY"; public static final String CHECK_COMMAND_PERMANENT_COUNT = "PERMANENT_COUNT"; + public static final String CHECK_COMMAND_PERMANENT_COUNTERS = "PERMANENT_COUNTERS"; public static final String CHECK_COMMAND_EXILE_COUNT = "EXILE_COUNT"; public static final String CHECK_COMMAND_HAND_COUNT = "HAND_COUNT"; public static final String CHECK_COMMAND_HAND_CARD_COUNT = "HAND_CARD_COUNT"; public static final String CHECK_COMMAND_COLOR = "COLOR"; + public static final String CHECK_COMMAND_TYPE = "TYPE"; public static final String CHECK_COMMAND_SUBTYPE = "SUBTYPE"; public static final String CHECK_COMMAND_MANA_POOL = "MANA_POOL"; public static final String CHECK_COMMAND_ALIAS_ZONE = "ALIAS_ZONE"; + public static final String CHECK_COMMAND_PLAYER_IN_GAME = "PLAYER_IN_GAME"; // TODO: add target player param to commands public static final String SHOW_COMMAND_LIBRARY = "LIBRARY"; public static final String SHOW_COMMAND_HAND = "HAND"; + public static final String SHOW_COMMAND_COMMAND = "COMMAND"; public static final String SHOW_COMMAND_BATTLEFIELD = "BATTLEFIELD"; public static final String SHOW_COMMAND_GRAVEYEARD = "GRAVEYARD"; public static final String SHOW_COMMAND_EXILE = "EXILE"; @@ -231,11 +236,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement + " (found actions after stop on " + maxTurn + " / " + maxPhase + ")", (maxTurn > this.stopOnTurn) || (maxTurn == this.stopOnTurn && maxPhase > this.stopAtStep.getIndex())); + for (Player player : currentGame.getPlayers().values()) { TestPlayer testPlayer = (TestPlayer) player; currentGame.cheat(player.getId(), getCommands(testPlayer)); - currentGame.cheat(player.getId(), getLibraryCards(testPlayer), getHandCards(testPlayer), - getBattlefieldCards(testPlayer), getGraveCards(testPlayer)); + currentGame.cheat(player.getId(), activePlayer.getId(), getLibraryCards(testPlayer), getHandCards(testPlayer), + getBattlefieldCards(testPlayer), getGraveCards(testPlayer), getCommandCards(testPlayer)); } long t1 = System.nanoTime(); @@ -293,10 +299,19 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement check(checkName, turnNum, step, player, CHECK_COMMAND_PT, permanentName, power.toString(), toughness.toString()); } + public void checkDamage(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer damage) { + //Assert.assertNotEquals("", permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_DAMAGE, permanentName, damage.toString()); + } + public void checkLife(String checkName, int turnNum, PhaseStep step, TestPlayer player, Integer life) { check(checkName, turnNum, step, player, CHECK_COMMAND_LIFE, life.toString()); } + public void checkPlayerInGame(String checkName, int turnNum, PhaseStep step, TestPlayer player, TestPlayer targetPlayer, Boolean mustBeInGame) { + check(checkName, turnNum, step, player, CHECK_COMMAND_PLAYER_IN_GAME, targetPlayer.getId().toString(), mustBeInGame.toString()); + } + public void checkAbility(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Class abilityClass, Boolean mustHave) { //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_ABILITY, permanentName, abilityClass.getName(), mustHave.toString()); @@ -304,7 +319,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) { //Assert.assertNotEquals("", permanentName); - check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, permanentName, count.toString()); + checkPermanentCount(checkName, turnNum, step, player, player, permanentName, count); + } + + public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, TestPlayer targetPlayer, String permanentName, Integer count) { + //Assert.assertNotEquals("", permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, targetPlayer.getId().toString(), permanentName, count.toString()); + } + + public void checkPermanentCounters(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, CounterType counterType, Integer count) { + check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNTERS, permanentName, counterType.toString(), count.toString()); } public void checkExileCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) { @@ -326,6 +350,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement check(checkName, turnNum, step, player, CHECK_COMMAND_COLOR, permanentName, colors, mustHave.toString()); } + public void checkType(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, CardType type, Boolean mustHave) { + //Assert.assertNotEquals("", permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_TYPE, permanentName, type.toString(), mustHave.toString()); + } + public void checkSubType(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, SubType subType, Boolean mustHave) { //Assert.assertNotEquals("", permanentName); check(checkName, turnNum, step, player, CHECK_COMMAND_SUBTYPE, permanentName, subType.toString(), mustHave.toString()); @@ -361,6 +390,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement show(showName, turnNum, step, player, SHOW_COMMAND_HAND); } + public void showCommand(String showName, int turnNum, PhaseStep step, TestPlayer player) { + show(showName, turnNum, step, player, SHOW_COMMAND_COMMAND); + } + public void showBattlefield(String showName, int turnNum, PhaseStep step, TestPlayer player) { show(showName, turnNum, step, player, SHOW_COMMAND_BATTLEFIELD); } @@ -508,6 +541,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement return getGraveCards(player); case LIBRARY: return getLibraryCards(player); + case COMMAND: + return getCommandCards(player); default: break; } @@ -1225,6 +1260,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement + "])", count, player.getActions().size()); } + public void assertActionsMustBeEmpty(TestPlayer player) throws AssertionError { + if (!player.getActions().isEmpty()) { + System.out.println("Remaining actions for " + player.getName() + " (" + player.getActions().size() + "):"); + player.getActions().stream().forEach(a -> { + System.out.println("* turn " + a.getTurnNum() + " - " + a.getStep() + ": " + (a.getActionName().isEmpty() ? a.getAction() : a.getActionName())); + }); + Assert.fail("Player " + player.getName() + " must have 0 actions but found " + player.getActions().size()); + } + } + public void assertChoicesCount(TestPlayer player, int count) throws AssertionError { Assert.assertEquals("(Choices of " + player.getName() + ") Count are not equal (found " + player.getChoices() + ")", count, player.getChoices().size()); } @@ -1236,7 +1281,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void assertAllCommandsUsed() throws AssertionError { for (Player player : currentGame.getPlayers().values()) { TestPlayer testPlayer = (TestPlayer) player; - assertActionsCount(testPlayer, 0); + assertActionsMustBeEmpty(testPlayer); assertChoicesCount(testPlayer, 0); assertTargetsCount(testPlayer, 0); } diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 384d89c21f..6fe462512f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -583,22 +583,22 @@ public class PlayerStub implements Player { } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game) { + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game) { return false; } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, boolean triggerEvents) { + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, boolean triggerEvents) { return false; } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId) { + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId) { return false; } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents) { + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents) { return false; } @@ -1092,7 +1092,7 @@ public class PlayerStub implements Player { } @Override - public boolean lookAtFaceDownCard(Card card, Game game) { + public boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate) { return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ExportJsonGameplayDataTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ExportJsonGameplayDataTest.java index 67ef4b6431..624c07c13f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ExportJsonGameplayDataTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ExportJsonGameplayDataTest.java @@ -37,8 +37,16 @@ public class ExportJsonGameplayDataTest { Collection sets = Sets.getInstance().values(); for (ExpansionSet set : sets) { for (ExpansionSet.SetCardInfo setInfo : set.getSetCardInfo()) { - cards.add(CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(), - setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()))); + // catch cards creation errors and report (e.g. on wrong card code) + try { + Card card = CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(), + setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo())); + if (card != null) { + cards.add(card); + } + } catch (Throwable e) { + logger.error("Can't create card " + setInfo.getName() + ": " + e.getMessage(), e); + } } JsonObject res = new JsonObject(); diff --git a/Mage.Updater/pom.xml b/Mage.Updater/pom.xml index d5144a71a5..b92840266b 100644 --- a/Mage.Updater/pom.xml +++ b/Mage.Updater/pom.xml @@ -5,7 +5,7 @@ mage-root org.mage - 1.4.34 + 1.4.35 4.0.0 diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml index 3841e63662..c975c7ee0c 100644 --- a/Mage.Verify/pom.xml +++ b/Mage.Verify/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage-verify @@ -49,7 +49,7 @@ org.mage mage-client - 1.4.34 + 1.4.35 diff --git a/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java b/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java index b57dd050df..b948044472 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonLegalities.java @@ -1,7 +1,11 @@ package mage.verify; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import static mage.verify.MtgJson.MTGJSON_IGNORE_NEW_PROPERTIES; + +@JsonIgnoreProperties(ignoreUnknown = MTGJSON_IGNORE_NEW_PROPERTIES) public class JsonLegalities { @JsonProperty("1v1") public String oneVersusOne; diff --git a/Mage.Verify/src/main/java/mage/verify/JsonMeta.java b/Mage.Verify/src/main/java/mage/verify/JsonMeta.java index 4d68703479..af617214bd 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonMeta.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonMeta.java @@ -1,5 +1,10 @@ package mage.verify; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import static mage.verify.MtgJson.MTGJSON_IGNORE_NEW_PROPERTIES; + +@JsonIgnoreProperties(ignoreUnknown = MTGJSON_IGNORE_NEW_PROPERTIES) public class JsonMeta { public String date; public String version; diff --git a/Mage.Verify/src/main/java/mage/verify/JsonRuling.java b/Mage.Verify/src/main/java/mage/verify/JsonRuling.java index 1576700d58..77ae25709e 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonRuling.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonRuling.java @@ -1,5 +1,10 @@ package mage.verify; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import static mage.verify.MtgJson.MTGJSON_IGNORE_NEW_PROPERTIES; + +@JsonIgnoreProperties(ignoreUnknown = MTGJSON_IGNORE_NEW_PROPERTIES) public class JsonRuling { public String date; public String text; diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index d2656c2a4d..4ea8fb15cb 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -15,6 +15,7 @@ import mage.game.draft.RateCard; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; import mage.watchers.Watcher; +import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -41,6 +42,8 @@ import java.util.stream.Collectors; */ public class VerifyCardDataTest { + private static final Logger logger = Logger.getLogger(VerifyCardDataTest.class); + // right now this is very noisy, and not useful enough to make any assertions on private static final boolean CHECK_SOURCE_TOKENS = false; @@ -76,6 +79,7 @@ public class VerifyCardDataTest { skipListAddName("COST", "M13", "Erase"); skipListAddName("COST", "ULG", "Erase"); skipListAddName("COST", "H17", "Grimlock, Dinobot Leader"); + skipListAddName("COST", "UST", "Everythingamajig"); // supertype skipListCreate("SUPERTYPE"); @@ -87,6 +91,7 @@ public class VerifyCardDataTest { // subtype skipListCreate("SUBTYPE"); + skipListAddName("SUBTYPE", "UGL", "Miss Demeanor"); // number skipListCreate("NUMBER"); @@ -193,6 +198,7 @@ public class VerifyCardDataTest { if (!needClass.equals(currentClass)) { // workaround to star wars and unstable set with same card names if (!checkCard.getName().equals("Syndicate Enforcer") + && !checkCard.getName().equals("Everythingamajig") && !checkCard.getName().equals("Garbage Elemental") && !checkCard.getName().equals("Very Cryptic Command")) { errorsList.add("Error: found wrong class in set " + set.getCode() + " - " + checkCard.getName() + " (" + currentClass + " <> " + needClass + ")"); @@ -599,7 +605,7 @@ public class VerifyCardDataTest { //checkNumbers(card, ref); // TODO: load data from allsets.json and check it (allcards.json do not have card numbers) checkBasicLands(card, ref); checkMissingAbilities(card, ref); - //checkWrongCosts(card, ref); + checkWrongAbilitiesText(card, ref); } private void checkColors(Card card, JsonCard ref) { @@ -731,13 +737,23 @@ public class VerifyCardDataTest { card.getRules().stream().forEach(System.out::println); } - private void checkWrongCosts(Card card, JsonCard ref) { - // checks missing or wrong cost - if (!card.getExpansionSetCode().equals("RNA")) { + private void checkWrongAbilitiesText(Card card, JsonCard ref) { + // checks missing or wrong text + if (!card.getExpansionSetCode().equals("WAR")) { return; } - String[] refRules = ref.text.split("[\\$\\\n]"); // ref card's abilities can be splited by \n or $ chars + if (ref.text == null || ref.text.isEmpty()) { + return; + } + + String refText = ref.text; + // lands fix + if (refText.startsWith("(") && refText.endsWith(")")) { + refText = refText.substring(1, refText.length() - 1); + } + + String[] refRules = refText.split("[\\$\\\n]"); // ref card's abilities can be splited by \n or $ chars for (int i = 0; i < refRules.length; i++) { refRules[i] = prepareRule(card.getName(), refRules[i]); } @@ -749,17 +765,15 @@ public class VerifyCardDataTest { for (String cardRule : cardRules) { boolean isAbilityFounded = false; - boolean isCostOk = false; for (String refRule : refRules) { if (cardRule.equals(refRule)) { isAbilityFounded = true; - isCostOk = true; break; } } - if (!isCostOk) { - fail(card, "abilities", "card ability have cost, but can't find in ref [" + "xxx" + ": " + card.getRules() + "]"); + if (!isAbilityFounded) { + warn(card, "card ability can't be found in ref [" + card.getName() + ": " + cardRule + "]"); } } } @@ -960,4 +974,28 @@ public class VerifyCardDataTest { } } } + + @Test + public void testCardsCreatingAndConstructorErrors() { + int errorsCount = 0; + Collection sets = Sets.getInstance().values(); + for (ExpansionSet set : sets) { + for (ExpansionSet.SetCardInfo setInfo : set.getSetCardInfo()) { + // catch cards creation errors and report (e.g. on wrong card code or construction checks fail) + try { + Card card = CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(), + setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo())); + if (card == null) { + errorsCount++; + } + } catch (Throwable e) { + logger.error("Can't create card " + setInfo.getName() + ": " + e.getMessage(), e); + } + } + } + + if (errorsCount > 0) { + Assert.fail("Founded " + errorsCount + " broken cards, look at logs for stack error"); + } + } } diff --git a/Mage/pom.xml b/Mage/pom.xml index 067457c221..1b972e0326 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 mage diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java index 44f63bebc6..a4f2a81302 100644 --- a/Mage/src/main/java/mage/MageObjectReference.java +++ b/Mage/src/main/java/mage/MageObjectReference.java @@ -53,6 +53,10 @@ public class MageObjectReference implements Comparable, Ser public MageObjectReference(UUID sourceId, Game game) { this.sourceId = sourceId; + if (sourceId == null) { + throw new IllegalArgumentException("MageObjectReference contains nullable sourceId"); + } + MageObject mageObject = game.getObject(sourceId); if (mageObject != null) { this.zoneChangeCounter = mageObject.getZoneChangeCounter(game); diff --git a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java index d12963c839..c1d4d0e7f0 100644 --- a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbilities.java @@ -1,5 +1,3 @@ - - package mage.abilities; import mage.constants.Duration; @@ -48,8 +46,8 @@ public class DelayedTriggeredAbilities extends AbilitiesImpl ability.getDuration() == Duration.EndOfTurn); + public void removeEndOfTurnAbilities(Game game) { + this.removeIf(ability -> ability.getDuration() == Duration.EndOfTurn); // TODO: add Duration.EndOfYourTurn like effects } public void removeEndOfCombatAbilities() { diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 98def5bddb..c7eb75fbb0 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -335,26 +335,31 @@ public class Modes extends LinkedHashMap { if (this.getMaxModesFilter() != null) { sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage()); } else if (this.getMinModes() == 0 && this.getMaxModes() == 1) { - sb.append("choose up to one "); + sb.append("choose up to one"); } else if (this.getMinModes() == 1 && this.getMaxModes() > 2) { - sb.append("choose one or more "); + sb.append("choose one or more"); } else if (this.getMinModes() == 1 && this.getMaxModes() == 2) { - sb.append("choose one or both "); + sb.append("choose one or both"); } else if (this.getMinModes() == 2 && this.getMaxModes() == 2) { - sb.append("choose two "); + sb.append("choose two"); } else if (this.getMinModes() == 3 && this.getMaxModes() == 3) { - sb.append("choose three "); + sb.append("choose three"); + } else if (this.getMinModes() == 4 && this.getMaxModes() == 4) { + sb.append("choose four"); } else { - sb.append("choose one "); + sb.append("choose one"); } + if (isEachModeOnlyOnce()) { - sb.append("that hasn't been chosen "); + sb.append(" that hasn't been chosen"); } + if (isEachModeMoreThanOnce()) { sb.append(". You may choose the same mode more than once.
"); } else { - sb.append("—
"); + sb.append(" —
"); } + for (Mode mode : this.values()) { sb.append("&bull "); sb.append(mode.getEffects().getTextStartingUpperCase(mode)); diff --git a/Mage/src/main/java/mage/abilities/abilityword/KinshipAbility.java b/Mage/src/main/java/mage/abilities/abilityword/KinshipAbility.java index f6b952bbfb..6c28ca572f 100644 --- a/Mage/src/main/java/mage/abilities/abilityword/KinshipAbility.java +++ b/Mage/src/main/java/mage/abilities/abilityword/KinshipAbility.java @@ -1,5 +1,3 @@ - - package mage.abilities.abilityword; import mage.abilities.Ability; @@ -21,28 +19,27 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; /** - * * @author LevelX2 */ public class KinshipAbility extends TriggeredAbilityImpl { - + public KinshipAbility(Effect kinshipEffect) { - super(Zone.BATTLEFIELD, new KinshipBaseEffect(kinshipEffect), true); + super(Zone.BATTLEFIELD, new KinshipBaseEffect(kinshipEffect), true); } - + public KinshipAbility(final KinshipAbility ability) { - super(ability); + super(ability); } public void addKinshipEffect(Effect kinshipEffect) { - for (Effect effect: this.getEffects()) { + for (Effect effect : this.getEffects()) { if (effect instanceof KinshipBaseEffect) { - ((KinshipBaseEffect) effect).addEffect(kinshipEffect); - break; + ((KinshipBaseEffect) effect).addEffect(kinshipEffect); + break; } - } + } } - + @Override public KinshipAbility copy() { return new KinshipAbility(this); @@ -62,33 +59,33 @@ public class KinshipAbility extends TriggeredAbilityImpl { public String getRule() { return new StringBuilder("Kinship — At the beginning of your upkeep, ").append(super.getRule()).toString(); } - + } class KinshipBaseEffect extends OneShotEffect { - + private final Effects kinshipEffects = new Effects(); - + public KinshipBaseEffect(Effect kinshipEffect) { super(kinshipEffect.getOutcome()); this.kinshipEffects.add(kinshipEffect); this.staticText = "you may look at the top card of your library. If it shares a creature type with {this}, you may reveal it. If you do, "; } - + public KinshipBaseEffect(final KinshipBaseEffect effect) { super(effect); - this.kinshipEffects.addAll(effect.kinshipEffects); + this.kinshipEffects.addAll(effect.kinshipEffects.copy()); } - + public void addEffect(Effect kinshipEffect) { this.kinshipEffects.add(kinshipEffect); } - + @Override public KinshipBaseEffect copy() { return new KinshipBaseEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); @@ -100,22 +97,22 @@ class KinshipBaseEffect extends OneShotEffect { Cards cards = new CardsImpl(card); controller.lookAtCards(sourcePermanent.getName(), cards, game); if (sourcePermanent.shareSubtypes(card, game)) { - if (controller.chooseUse(outcome,new StringBuilder("Kinship - Reveal ").append(card.getLogName()).append('?').toString(), source, game)) { + if (controller.chooseUse(outcome, new StringBuilder("Kinship - Reveal ").append(card.getLogName()).append('?').toString(), source, game)) { controller.revealCards(sourcePermanent.getName(), cards, game); - for (Effect effect: kinshipEffects) { + for (Effect effect : kinshipEffects) { effect.setTargetPointer(new FixedTarget(card.getId())); if (effect.getEffectType() == EffectType.ONESHOT) { effect.apply(game, source); } else { if (effect instanceof ContinuousEffect) { - game.addEffect((ContinuousEffect)effect, source); + game.addEffect((ContinuousEffect) effect, source); } else { throw new UnsupportedOperationException("This kind of effect is not supported"); } } - } + } } - } + } } } return true; @@ -125,7 +122,7 @@ class KinshipBaseEffect extends OneShotEffect { @Override public String getText(Mode mode) { - return new StringBuilder(super.getText(mode)).append(kinshipEffects.getText(mode)).toString(); + return new StringBuilder(super.getText(mode)).append(kinshipEffects.getText(mode)).toString(); } - + } diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java index 49d7c0a70e..87ed534a99 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java @@ -70,6 +70,16 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { } } return yours; + case NOT_YOU: + boolean notYours = !event.getPlayerId().equals(this.controllerId); + if (notYours && setTargetPointer) { + if (getTargets().isEmpty()) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getPlayerId())); + } + } + } + return notYours; case OPPONENT: if (game.getPlayer(this.controllerId).hasOpponent(event.getPlayerId(), game)) { if (setTargetPointer && getTargets().isEmpty()) { diff --git a/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java b/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java index be7896e11b..c31137dfe8 100644 --- a/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastCommanderAbility.java @@ -1,7 +1,7 @@ - package mage.abilities.common; import mage.abilities.SpellAbility; +import mage.abilities.costs.CostsImpl; import mage.cards.Card; import mage.constants.SpellAbilityType; import mage.constants.TimingRule; @@ -9,15 +9,21 @@ import mage.constants.Zone; import mage.game.Game; /** - * * @author Plopman */ public class CastCommanderAbility extends SpellAbility { public CastCommanderAbility(Card card) { super(card.getManaCost(), card.getName(), Zone.COMMAND, SpellAbilityType.BASE); - this.costs = card.getSpellAbility().getCosts().copy(); - this.timing = TimingRule.SORCERY; + if (card.getSpellAbility() != null) { + this.getCosts().addAll(card.getSpellAbility().getCosts().copy()); + this.getEffects().addAll(card.getSpellAbility().getEffects().copy()); + this.getTargets().addAll(card.getSpellAbility().getTargets().copy()); + this.timing = card.getSpellAbility().getTiming(); + } else { + this.costs = new CostsImpl<>(); + this.timing = TimingRule.SORCERY; + } this.usesStack = true; this.controllerId = card.getOwnerId(); this.sourceId = card.getId(); diff --git a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageToAPlayerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageToAPlayerTriggeredAbility.java index acf69c2ba1..e05f4cf3bb 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageToAPlayerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageToAPlayerTriggeredAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; @@ -45,6 +44,7 @@ public class DealsCombatDamageToAPlayerTriggeredAbility extends TriggeredAbility this.text = ability.text; this.setTargetPointer = ability.setTargetPointer; this.onlyOpponents = ability.onlyOpponents; + this.orPlaneswalker = ability.orPlaneswalker; } public DealsCombatDamageToAPlayerTriggeredAbility setOrPlaneswalker(boolean orPlaneswalker) { diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java new file mode 100644 index 0000000000..a7e9205f71 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java @@ -0,0 +1,92 @@ +package mage.abilities.common; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { + + public GodEternalDiesTriggeredAbility() { + super(Zone.ALL, null, true); + } + + private GodEternalDiesTriggeredAbility(GodEternalDiesTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + return zEvent.getFromZone() == Zone.BATTLEFIELD + && (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED); + } + return false; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getTargetId().equals(this.getSourceId())) { + this.getEffects().clear(); + this.addEffect(new GodEternalEffect(new MageObjectReference(zEvent.getTarget(), game))); + return true; + } + return false; + } + + @Override + public GodEternalDiesTriggeredAbility copy() { + return new GodEternalDiesTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When {this} dies or is put into exile from the battlefield, " + + "you may put it into its owner's library third from the top."; + } +} + +class GodEternalEffect extends OneShotEffect { + + private final MageObjectReference mor; + + GodEternalEffect(MageObjectReference mor) { + super(Outcome.Benefit); + this.mor = mor; + } + + private GodEternalEffect(final GodEternalEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public GodEternalEffect copy() { + return new GodEternalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = game.getCard(mor.getSourceId()); + if (card.getZoneChangeCounter(game) - 1 != mor.getZoneChangeCounter()) { + return false; + } + return player.putCardOnTopXOfLibrary(card, game, source, 3); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/condition/common/TargetHasCardTypeCondition.java b/Mage/src/main/java/mage/abilities/condition/common/TargetHasCardTypeCondition.java index d1f0bb30af..67a02f25c0 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/TargetHasCardTypeCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/TargetHasCardTypeCondition.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package mage.abilities.condition.common; import mage.MageObject; diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java index 90c7b07cd9..ab1196345f 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java @@ -1,9 +1,5 @@ - package mage.abilities.costs.common; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; @@ -18,8 +14,11 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX */ public class RemoveCounterCost extends CostImpl { @@ -102,10 +101,6 @@ public class RemoveCounterCost extends CostImpl { new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game); } permanent.removeCounters(counterName, numberOfCountersSelected, game); - if (permanent.getCounters(game).getCount(counterName) == 0) { - // this removes only the item with number = 0 from the collection - permanent.getCounters(game).removeCounter(counterName); - } countersRemoved += numberOfCountersSelected; if (!game.isSimulation()) { game.informPlayers(new StringBuilder(controller.getLogName()) diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java index 1ce5392da3..df8c2f51e5 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java @@ -1,9 +1,5 @@ package mage.abilities.decorator; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.condition.Condition; @@ -11,11 +7,14 @@ import mage.abilities.condition.FixedCondition; import mage.abilities.condition.LockedInCondition; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; -import mage.constants.DependencyType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.SubLayer; +import mage.constants.*; import mage.game.Game; +import org.junit.Assert; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; /** * Adds condition to {@link ContinuousEffect}. Acts as decorator. @@ -48,6 +47,18 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { this.otherwiseEffect = otherwiseEffect; this.baseCondition = condition; this.staticText = text; + + // checks for compatibility + EffectType needType = EffectType.CONTINUOUS; + if (effect != null && !effect.getEffectType().equals(needType)) { + Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString()); + } + if (otherwiseEffect != null && !otherwiseEffect.getEffectType().equals(needType)) { + Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString()); + } + if (effect != null && otherwiseEffect != null && !effect.getEffectType().equals(otherwiseEffect.getEffectType())) { + Assert.fail("ConditionalContinuousEffect must be same but found " + effect.getEffectType().toString() + " and " + otherwiseEffect.getEffectType().toString()); + } } public ConditionalContinuousEffect(final ConditionalContinuousEffect effect) { @@ -68,6 +79,7 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { + super.init(source, game); if (baseCondition instanceof LockedInCondition) { condition = new FixedCondition(((LockedInCondition) baseCondition).getBaseCondition().apply(game, source)); } else { diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java new file mode 100644 index 0000000000..cecf01c624 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalCostModificationEffect.java @@ -0,0 +1,86 @@ +package mage.abilities.decorator; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.effects.CostModificationEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.constants.Duration; +import mage.game.Game; + +/** + * @author JayDi85 + */ +public class ConditionalCostModificationEffect extends CostModificationEffectImpl { + + protected CostModificationEffect effect; + protected CostModificationEffect otherwiseEffect; + protected Condition condition; + protected boolean conditionState; + + public ConditionalCostModificationEffect(CostModificationEffect effect, Condition condition, String text) { + this(effect, condition, null, text); + } + + public ConditionalCostModificationEffect(CostModificationEffect effect, Condition condition, CostModificationEffect otherwiseEffect, + String text) { + super(effect.getDuration(), effect.getOutcome(), effect.getModificationType()); + this.effect = effect; + this.condition = condition; + this.otherwiseEffect = otherwiseEffect; + if (text != null) { + this.setText(text); + } + } + + public ConditionalCostModificationEffect(final ConditionalCostModificationEffect effect) { + super(effect); + this.effect = (CostModificationEffect) effect.effect.copy(); + if (effect.otherwiseEffect != null) { + this.otherwiseEffect = (CostModificationEffect) effect.otherwiseEffect.copy(); + } + this.condition = effect.condition; + this.conditionState = effect.conditionState; + } + + @Override + public boolean isDiscarded() { + return effect.isDiscarded() || (otherwiseEffect != null && otherwiseEffect.isDiscarded()); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + conditionState = condition.apply(game, source); + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.apply(game, source, abilityToModify); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.apply(game, source, abilityToModify); + } + if (!conditionState && effect.getDuration() == Duration.OneUse) { + used = true; + } + if (!conditionState && effect.getDuration() == Duration.Custom) { + this.discard(); + } + return false; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + conditionState = condition.apply(game, source); + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.applies(abilityToModify, source, game); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.applies(abilityToModify, source, game); + } + return false; + } + + @Override + public ConditionalCostModificationEffect copy() { + return new ConditionalCostModificationEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java new file mode 100644 index 0000000000..ec256f06dc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalPreventionEffect.java @@ -0,0 +1,140 @@ +package mage.abilities.decorator; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.Condition; +import mage.abilities.condition.FixedCondition; +import mage.abilities.condition.LockedInCondition; +import mage.abilities.effects.PreventionEffect; +import mage.abilities.effects.PreventionEffectImpl; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author JayDi85 + */ +public class ConditionalPreventionEffect extends PreventionEffectImpl { + + protected PreventionEffect effect; + protected PreventionEffect otherwiseEffect; + protected Condition baseCondition; + protected Condition condition; + protected boolean conditionState; + protected boolean initDone = false; + + public ConditionalPreventionEffect(PreventionEffect effect, Condition condition, String text) { + this(effect, null, condition, text); + } + + /** + * Only use this if both effects have the same layers + * + * @param effect + * @param otherwiseEffect + * @param condition + * @param text + */ + public ConditionalPreventionEffect(PreventionEffect effect, PreventionEffect otherwiseEffect, Condition condition, String text) { + super(effect.getDuration()); + this.effect = effect; + this.otherwiseEffect = otherwiseEffect; + this.baseCondition = condition; + this.staticText = text; + } + + public ConditionalPreventionEffect(final ConditionalPreventionEffect effect) { + super(effect); + this.effect = (PreventionEffect) effect.effect.copy(); + if (effect.otherwiseEffect != null) { + this.otherwiseEffect = (PreventionEffect) effect.otherwiseEffect.copy(); + } + this.condition = effect.condition; // TODO: checks conditional copy -- it's can be usefull for memory leaks fix? + this.conditionState = effect.conditionState; + this.baseCondition = effect.baseCondition; + this.initDone = effect.initDone; + } + + @Override + public boolean isDiscarded() { + return this.discarded || effect.isDiscarded() || (otherwiseEffect != null && otherwiseEffect.isDiscarded()); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + if (baseCondition instanceof LockedInCondition) { + condition = new FixedCondition(((LockedInCondition) baseCondition).getBaseCondition().apply(game, source)); + } else { + condition = baseCondition; + } + effect.setTargetPointer(this.targetPointer); + effect.init(source, game); + if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + otherwiseEffect.init(source, game); + } + initDone = true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.replaceEvent(event, source, game); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.replaceEvent(event, source, game); + } + + if (!conditionState && effect.getDuration() == Duration.OneUse) { + used = true; + } + if (!conditionState && effect.getDuration() == Duration.Custom) { + this.discard(); + } + + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return effect.checksEventType(event, game) + || (otherwiseEffect != null && otherwiseEffect.checksEventType(event, game)); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!initDone) { // if simpleStaticAbility, init won't be called + init(source, game); + } + conditionState = condition.apply(game, source); + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.applies(event, source, game); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.applies(event, source, game); + } + return false; + } + + @Override + public String getText(Mode mode) { + if ((staticText == null || staticText.isEmpty()) && this.effect != null) { // usefull for conditional night/day card abilities + return effect.getText(mode); + } + return staticText; + } + + @Override + public ConditionalPreventionEffect copy() { + return new ConditionalPreventionEffect(this); + } + +} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java index 5ebe3b6172..148be62172 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalRequirementEffect.java @@ -1,7 +1,5 @@ - package mage.abilities.decorator; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.abilities.condition.FixedCondition; @@ -12,12 +10,13 @@ import mage.constants.EffectType; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author LevelX2 */ -public class ConditionalRequirementEffect extends RequirementEffect { +public class ConditionalRequirementEffect extends RequirementEffect { protected RequirementEffect effect; protected RequirementEffect otherwiseEffect; @@ -27,7 +26,14 @@ public class ConditionalRequirementEffect extends RequirementEffect { protected boolean initDone = false; public ConditionalRequirementEffect(RequirementEffect effect, Condition condition) { - this(Duration.WhileOnBattlefield, effect, condition, null, false); + this(effect, condition, null); + } + + public ConditionalRequirementEffect(RequirementEffect effect, Condition condition, String text) { + this(effect.getDuration(), effect, condition, null, false); + if (text != null) { + setText(text); + } } public ConditionalRequirementEffect(Duration duration, RequirementEffect effect, Condition condition, RequirementEffect otherwiseEffect, boolean lockedInCondition) { @@ -75,7 +81,7 @@ public class ConditionalRequirementEffect extends RequirementEffect { conditionState = condition.apply(game, source); if (conditionState) { effect.setTargetPointer(this.targetPointer); - return effect.applies(permanent, source,game); + return effect.applies(permanent, source, game); } else if (otherwiseEffect != null) { otherwiseEffect.setTargetPointer(this.targetPointer); return otherwiseEffect.applies(permanent, source, game); @@ -138,7 +144,7 @@ public class ConditionalRequirementEffect extends RequirementEffect { } return null; } - + @Override public ConditionalRequirementEffect copy() { return new ConditionalRequirementEffect(this); diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java index 5f1e6318d5..f9dec0248a 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalRestrictionEffect.java @@ -22,14 +22,25 @@ public class ConditionalRestrictionEffect extends RestrictionEffect { protected boolean initDone = false; public ConditionalRestrictionEffect(RestrictionEffect effect, Condition condition) { - this(Duration.WhileOnBattlefield, effect, condition, null); + this(effect, condition, null); + } + + public ConditionalRestrictionEffect(RestrictionEffect effect, Condition condition, String text) { + this(effect.getDuration(), effect, condition, null, text); } public ConditionalRestrictionEffect(Duration duration, RestrictionEffect effect, Condition condition, RestrictionEffect otherwiseEffect) { + this(duration, effect, condition, otherwiseEffect, null); + } + + public ConditionalRestrictionEffect(Duration duration, RestrictionEffect effect, Condition condition, RestrictionEffect otherwiseEffect, String text) { super(duration); this.effect = effect; this.baseCondition = condition; this.otherwiseEffect = otherwiseEffect; + if (text != null) { + this.setText(text); + } } public ConditionalRestrictionEffect(final ConditionalRestrictionEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestPowerAmongControlledCreaturesValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestPowerAmongControlledCreaturesValue.java index 714cc80a63..8989267cc2 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestPowerAmongControlledCreaturesValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestPowerAmongControlledCreaturesValue.java @@ -4,7 +4,7 @@ package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -17,17 +17,13 @@ public enum GreatestPowerAmongControlledCreaturesValue implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - Player player = game.getPlayer(sourceAbility.getControllerId()); - if (player != null) { - int amount = 0; - for (Permanent p : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), sourceAbility.getControllerId(), game)) { - if (p.getPower().getValue() > amount) { - amount = p.getPower().getValue(); - } - } - return amount; + int amount = 0; + for (Permanent p : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, sourceAbility.getControllerId(), game + )) { + amount = Math.max(p.getPower().getValue(), amount); } - return 0; + return amount; } @Override diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java new file mode 100644 index 0000000000..2ffddc55f0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java @@ -0,0 +1,43 @@ + +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author TheElk801 + */ +public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int amount = 0; + for (Permanent p : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, sourceAbility.getControllerId(), game + )) { + amount = Math.max(p.getToughness().getValue(), amount); + } + return amount; + } + + @Override + public GreatestToughnessAmongControlledCreaturesValue copy() { + return GreatestToughnessAmongControlledCreaturesValue.instance; + } + + @Override + public String getMessage() { + return "the greatest toughness among creatures you control"; + } + + @Override + public String toString() { + return "X"; + } + +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/PermanentsYouControlCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/PermanentsYouControlCount.java new file mode 100644 index 0000000000..8eef2b5ca7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/PermanentsYouControlCount.java @@ -0,0 +1,35 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.filter.StaticFilters; +import mage.game.Game; + +/** + * @author JayDi85 + */ +public enum PermanentsYouControlCount implements DynamicValue { + + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_PERMANENT, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game); + } + + @Override + public PermanentsYouControlCount copy() { + return instance; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "permanents you control"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index b6a9288166..66f262c750 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -1,10 +1,5 @@ - package mage.abilities.effects; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.constants.DependencyType; @@ -13,8 +8,12 @@ import mage.constants.Layer; import mage.constants.SubLayer; import mage.game.Game; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public interface ContinuousEffect extends Effect { @@ -41,6 +40,8 @@ public interface ContinuousEffect extends Effect { void init(Ability source, Game game); + void init(Ability source, Game game, UUID activePlayerId); + Layer getLayer(); SubLayer getSublayer(); @@ -59,6 +60,14 @@ public interface ContinuousEffect extends Effect { void addDependedToType(DependencyType dependencyType); + void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId); + + UUID getStartingController(); + + void incYourTurnNumPlayed(); + + boolean isYourNextTurn(Game game); + @Override void newId(); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index f908f16fd9..7cc9ed6c86 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -1,12 +1,5 @@ - package mage.abilities.effects; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.MageSingleton; @@ -14,18 +7,14 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.DomainValue; import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; -import mage.constants.AbilityType; -import mage.constants.DependencyType; -import mage.constants.Duration; -import mage.constants.EffectType; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.game.Game; import mage.players.Player; +import java.util.*; + /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public abstract class ContinuousEffectImpl extends EffectImpl implements ContinuousEffect { @@ -49,9 +38,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu */ protected boolean characterDefining = false; - // until your next turn - protected int startingTurn; - protected UUID startingControllerId; + // until your next turn or until end of your next turn + private UUID startingControllerId; // player to checkss turns (can't different with real controller ability) + private boolean startingTurnWasActive; + private int yourTurnNumPlayed = 0; // turnes played after effect was created public ContinuousEffectImpl(Duration duration, Outcome outcome) { super(outcome); @@ -79,8 +69,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.affectedObjectsSet = effect.affectedObjectsSet; this.affectedObjectList.addAll(effect.affectedObjectList); this.temporary = effect.temporary; - this.startingTurn = effect.startingTurn; this.startingControllerId = effect.startingControllerId; + this.startingTurnWasActive = effect.startingTurnWasActive; + this.yourTurnNumPlayed = effect.yourTurnNumPlayed; this.dependencyTypes = effect.dependencyTypes; this.dependendToTypes = effect.dependendToTypes; this.characterDefining = effect.characterDefining; @@ -148,6 +139,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public void init(Ability source, Game game) { + init(source, game, game.getActivePlayerId()); + } + + @Override + public void init(Ability source, Game game, UUID activePlayerId) { targetPointer.init(game, source); //20100716 - 611.2c if (AbilityType.ACTIVATED == source.getAbilityType() @@ -170,23 +166,75 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.affectedObjectsSet = true; } } - startingTurn = game.getTurnNum(); - startingControllerId = source.getControllerId(); + setStartingControllerAndTurnNum(game, source.getControllerId(), activePlayerId); + } + + @Override + public UUID getStartingController() { + return startingControllerId; + } + + @Override + public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) { + this.startingControllerId = startingController; + this.startingTurnWasActive = activePlayerId != null && activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too + this.yourTurnNumPlayed = 0; + } + + @Override + public void incYourTurnNumPlayed() { + yourTurnNumPlayed++; + } + + @Override + public boolean isYourNextTurn(Game game) { + if (this.startingTurnWasActive) { + return yourTurnNumPlayed == 1 && game.isActivePlayer(startingControllerId); + } else { + return yourTurnNumPlayed == 0 && game.isActivePlayer(startingControllerId); + } } @Override public boolean isInactive(Ability source, Game game) { - if (duration == Duration.UntilYourNextTurn) { - Player player = game.getPlayer(startingControllerId); - if (player != null) { - if (player.isInGame()) { - return game.isActivePlayer(startingControllerId) && game.getTurnNum() != startingTurn; - } - return player.hasReachedNextTurnAfterLeaving(); - } - return true; + // YOUR turn checks + // until end of turn - must be checked on cleanup step, see rules 514.2 + // other must checked here (active and leave players), see rules 800.4 + switch (duration) { + case UntilYourNextTurn: + case UntilEndOfYourNextTurn: + break; + default: + return false; } - return false; + + // cheat engine put cards without play and calls direct applyEffects with clean -- need to ignore it + if (game.getActivePlayerId() == null) { + return false; + } + + boolean canDelete = false; + Player player = game.getPlayer(startingControllerId); + + // discard on start of turn for leave player + // 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn + // or until a specific point in that turn will last until that turn would have begun. + // They neither expire immediately nor last indefinitely. + switch (duration) { + case UntilYourNextTurn: + case UntilEndOfYourNextTurn: + canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving()); + } + + // discard on another conditions (start of your turn) + switch (duration) { + case UntilYourNextTurn: + if (player != null && player.isInGame()) { + canDelete = canDelete || this.isYourNextTurn(game); + } + } + + return canDelete; } @Override @@ -263,14 +311,6 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu } } return dependentToEffects; - /* - return allEffectsInLayer.stream() - .filter(effect -> effect.getDependencyTypes().contains(dependendToTypes)) - .map(Effect::getId) - .collect(Collectors.toSet()); - - } - return new HashSet<>();*/ } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 5f1bad6e5b..6cb9d0e770 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -1,9 +1,5 @@ package mage.abilities.effects; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; import mage.MageObject; import mage.MageObjectReference; import mage.abilities.*; @@ -31,6 +27,11 @@ import mage.players.Player; import mage.target.common.TargetCardInHand; import org.apache.log4j.Logger; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + /** * @author BetaSteward_at_googlemail.com */ @@ -54,7 +55,7 @@ public class ContinuousEffects implements Serializable { private final Map> asThoughEffectsMap = new EnumMap<>(AsThoughEffectType.class); public final List> allEffectsLists = new ArrayList<>(); private final ApplyCountersEffect applyCounters; -// private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect; + // private final PlaneswalkerRedirectionEffect planeswalkerRedirectionEffect; private final AuraReplacementEffect auraReplacementEffect; private final List previous = new ArrayList<>(); @@ -134,18 +135,18 @@ public class ContinuousEffects implements Serializable { spliceCardEffects.removeEndOfCombatEffects(); } - public synchronized void removeEndOfTurnEffects() { - layeredEffects.removeEndOfTurnEffects(); - continuousRuleModifyingEffects.removeEndOfTurnEffects(); - replacementEffects.removeEndOfTurnEffects(); - preventionEffects.removeEndOfTurnEffects(); - requirementEffects.removeEndOfTurnEffects(); - restrictionEffects.removeEndOfTurnEffects(); + public synchronized void removeEndOfTurnEffects(Game game) { + layeredEffects.removeEndOfTurnEffects(game); + continuousRuleModifyingEffects.removeEndOfTurnEffects(game); + replacementEffects.removeEndOfTurnEffects(game); + preventionEffects.removeEndOfTurnEffects(game); + requirementEffects.removeEndOfTurnEffects(game); + restrictionEffects.removeEndOfTurnEffects(game); for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) { - asThoughtlist.removeEndOfTurnEffects(); + asThoughtlist.removeEndOfTurnEffects(game); } - costModificationEffects.removeEndOfTurnEffects(); - spliceCardEffects.removeEndOfTurnEffects(); + costModificationEffects.removeEndOfTurnEffects(game); + spliceCardEffects.removeEndOfTurnEffects(game); } public synchronized void removeInactiveEffects(Game game) { @@ -163,6 +164,20 @@ public class ContinuousEffects implements Serializable { spliceCardEffects.removeInactiveEffects(game); } + public synchronized void incYourTurnNumPlayed(Game game) { + layeredEffects.incYourTurnNumPlayed(game); + continuousRuleModifyingEffects.incYourTurnNumPlayed(game); + replacementEffects.incYourTurnNumPlayed(game); + preventionEffects.incYourTurnNumPlayed(game); + requirementEffects.incYourTurnNumPlayed(game); + restrictionEffects.incYourTurnNumPlayed(game); + for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) { + asThoughtlist.incYourTurnNumPlayed(game); + } + costModificationEffects.incYourTurnNumPlayed(game); + spliceCardEffects.incYourTurnNumPlayed(game); + } + public synchronized List getLayeredEffects(Game game) { List layerEffects = new ArrayList<>(); for (ContinuousEffect effect : layeredEffects) { @@ -322,7 +337,7 @@ public class ContinuousEffects implements Serializable { } // boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT); //get all applicable transient Replacement effects - for (Iterator iterator = replacementEffects.iterator(); iterator.hasNext();) { + for (Iterator iterator = replacementEffects.iterator(); iterator.hasNext(); ) { ReplacementEffect effect = iterator.next(); if (!effect.checksEventType(event, game)) { continue; @@ -354,7 +369,8 @@ public class ContinuousEffects implements Serializable { replaceEffects.put(effect, applicableAbilities); } } - for (Iterator iterator = preventionEffects.iterator(); iterator.hasNext();) { + + for (Iterator iterator = preventionEffects.iterator(); iterator.hasNext(); ) { PreventionEffect effect = iterator.next(); if (!effect.checksEventType(event, game)) { continue; @@ -376,9 +392,10 @@ public class ContinuousEffects implements Serializable { } } if (!applicableAbilities.isEmpty()) { - replaceEffects.put((ReplacementEffect) effect, applicableAbilities); + replaceEffects.put(effect, applicableAbilities); } } + return replaceEffects; } @@ -478,7 +495,6 @@ public class ContinuousEffects implements Serializable { } /** - * * @param objectId * @param type * @param affectedAbility @@ -697,10 +713,10 @@ public class ContinuousEffects implements Serializable { * Checks if an event won't happen because of an rule modifying effect * * @param event - * @param targetAbility ability the event is attached to. can be null. + * @param targetAbility ability the event is attached to. can be null. * @param game * @param checkPlayableMode true if the event does not really happen but - * it's checked if the event would be replaced + * it's checked if the event would be replaced * @return */ public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) { @@ -747,7 +763,7 @@ public class ContinuousEffects implements Serializable { do { Map> rEffects = getApplicableReplacementEffects(event, game); // Remove all consumed effects (ability dependant) - for (Iterator it1 = rEffects.keySet().iterator(); it1.hasNext();) { + for (Iterator it1 = rEffects.keySet().iterator(); it1.hasNext(); ) { ReplacementEffect entry = it1.next(); if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9. Set consumedAbilitiesIds = consumed.get(entry.getId()); @@ -938,7 +954,7 @@ public class ContinuousEffects implements Serializable { if (!waitingEffects.isEmpty()) { // check if waiting effects can be applied now - for (Iterator>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) { + for (Iterator>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry> entry = iterator.next(); if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself appliedAbilities = appliedEffectAbilities.get(entry.getKey()); @@ -1059,9 +1075,7 @@ public class ContinuousEffects implements Serializable { final Card card = game.getPermanentOrLKIBattlefield(ability.getSourceId()); if (!(effect instanceof BecomesFaceDownCreatureEffect)) { if (card != null) { - if (!card.getAbilities(game).contains(ability)) { - return false; - } + return card.getAbilities(game).contains(ability); } } return true; diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java index 69837a16b7..ac714d8e4e 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java @@ -1,13 +1,17 @@ package mage.abilities.effects; -import java.util.*; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.MageSingleton; +import mage.cards.Card; import mage.constants.Duration; import mage.constants.Zone; import mage.game.Game; +import mage.players.Player; import org.apache.log4j.Logger; +import java.util.*; + /** * @param * @author BetaSteward_at_googlemail.com @@ -40,10 +44,21 @@ public class ContinuousEffectsList extends ArrayList return new ContinuousEffectsList<>(this); } - public void removeEndOfTurnEffects() { - for (Iterator i = this.iterator(); i.hasNext();) { + public void removeEndOfTurnEffects(Game game) { + // calls every turn on cleanup step (only end of turn duration) + // rules 514.2 + for (Iterator i = this.iterator(); i.hasNext(); ) { T entry = i.next(); - if (entry.getDuration() == Duration.EndOfTurn) { + boolean canRemove = false; + switch (entry.getDuration()) { + case EndOfTurn: + canRemove = true; + break; + case UntilEndOfYourNextTurn: + canRemove = entry.isYourNextTurn(game); + break; + } + if (canRemove) { i.remove(); effectAbilityMap.remove(entry.getId()); } @@ -52,7 +67,7 @@ public class ContinuousEffectsList extends ArrayList public void removeEndOfCombatEffects() { - for (Iterator i = this.iterator(); i.hasNext();) { + for (Iterator i = this.iterator(); i.hasNext(); ) { T entry = i.next(); if (entry.getDuration() == Duration.EndOfCombat) { i.remove(); @@ -62,7 +77,7 @@ public class ContinuousEffectsList extends ArrayList } public void removeInactiveEffects(Game game) { - for (Iterator i = this.iterator(); i.hasNext();) { + for (Iterator i = this.iterator(); i.hasNext(); ) { T entry = i.next(); if (isInactive(entry, game)) { i.remove(); @@ -71,7 +86,32 @@ public class ContinuousEffectsList extends ArrayList } } + public void incYourTurnNumPlayed(Game game) { + for (Iterator i = this.iterator(); i.hasNext(); ) { + T entry = i.next(); + if (game.isActivePlayer(entry.getStartingController())) { + entry.incYourTurnNumPlayed(); + } + } + } + private boolean isInactive(T effect, Game game) { + // ends all inactive effects -- calls on player leave or apply new effect + if (game.getState().isGameOver()) { + // no need to remove effects after end -- users and tests must see last game state + return false; + } + + /* + 800.4a When a player leaves the game, all objects (see rule 109) owned by that player leave the game and any effects + which give that player control of any objects or players end. Then, if that player controlled any objects on the stack + not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player, + those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game. + If the player who left the game had priority at the time he or she left, priority passes to the next player in turn + order who’s still in the game. + */ + // objects removes doing in player.leave() call... effects removes is here + Set set = effectAbilityMap.get(effect.getId()); if (set == null) { logger.debug("No abilities for effect found: " + effect.toString()); @@ -87,29 +127,62 @@ public class ContinuousEffectsList extends ArrayList } else if (effect.isDiscarded()) { it.remove(); } else { + // 800.4k When a player leaves the game, any continuous effects with durations that last until that + // player’s next turn or until a specific point in that turn will last until that turn would have begun. + // They neither expire immediately nor last indefinitely. + MageObject object = game.getObject(ability.getSourceId()); + boolean isObjectInGame = ability.getSourceId() == null || object != null; // Commander effects have no sourceId + boolean isOwnerLeaveGame = false; + if (object instanceof Card) { + Player owner = game.getPlayer(((Card) object).getOwnerId()); + isOwnerLeaveGame = !owner.isInGame(); + } + switch (effect.getDuration()) { + // case WhileOnBattlefield: case WhileInGraveyard: case WhileOnStack: - if (ability.getSourceId() != null && game.getObject(ability.getSourceId()) == null) { // Commander effects have no sourceId - it.remove(); // if the related source object does no longer exist in game - the effect has to be removed + case EndOfStep: + case EndOfCombat: + case EndOfGame: + // if the related source object does no longer exist in game - the effect has to be removed + if (isOwnerLeaveGame || !isObjectInGame) { + it.remove(); } break; case OneUse: - if (effect.isUsed()) { + if (isOwnerLeaveGame || effect.isUsed()) { it.remove(); } break; case Custom: + // custom effects must process it's own inactive method (override), but can'be missied by devs + if (isOwnerLeaveGame || effect.isInactive(ability, game)) { + it.remove(); + } + break; + case EndOfTurn: + // end of turn discards on cleanup steps + // 514.2 + break; case UntilYourNextTurn: + case UntilEndOfYourNextTurn: + // until your turn effects continue until real turn reached, their used it's own inactive method + // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents + // (including phased-out permanents) is removed and all "until end of turn" and "this turn" effects end. + // This turn-based action doesn’t use the stack. if (effect.isInactive(ability, game)) { it.remove(); } break; case UntilSourceLeavesBattlefield: - if (Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) { + if (isOwnerLeaveGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) { it.remove(); } + break; + default: + throw new IllegalStateException("Effects gets unknown duration " + effect.getDuration() + ", effect: " + effect.toString()); } } } @@ -151,7 +224,7 @@ public class ContinuousEffectsList extends ArrayList abilities.removeAll(abilitiesToRemove); } if (abilities == null || abilities.isEmpty()) { - for (Iterator iterator = this.iterator(); iterator.hasNext();) { + for (Iterator iterator = this.iterator(); iterator.hasNext(); ) { ContinuousEffect effect = iterator.next(); if (effect.getId().equals(effectIdToRemove)) { iterator.remove(); diff --git a/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java new file mode 100644 index 0000000000..81c1e19e28 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java @@ -0,0 +1,60 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.effects; + +import mage.abilities.Ability; +import mage.constants.Duration; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author antoni-g + */ +public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl { + + public PreventDamageAndRemoveCountersEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false); + staticText = "If damage would be dealt to {this}, prevent that damage and remove that many +1/+1 counters from it"; + } + + public PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) { + super(effect); + } + + @Override + public PreventDamageAndRemoveCountersEffect copy() { + return new PreventDamageAndRemoveCountersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + int damage = event.getAmount(); + preventDamageAction(event, source, game); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + permanent.removeCounters(CounterType.P1P1.createInstance(damage), game); //MTG ruling (this) loses counters even if the damage isn't prevented + } + return false; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (super.applies(event, source, game)) { + if (event.getTargetId().equals(source.getSourceId())) { + return true; + } + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java index 86edae8d1f..79aea129af 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java @@ -13,7 +13,6 @@ import mage.players.Player; import mage.util.CardUtil; /** - * * @author LevelX2 */ public class ChooseACardNameEffect extends OneShotEffect { @@ -92,11 +91,11 @@ public class ChooseACardNameEffect extends OneShotEffect { if (controller.choose(Outcome.Detriment, cardChoice, game)) { String cardName = cardChoice.getChoice(); if (!game.isSimulation()) { - game.informPlayers(sourceObject.getLogName() + ", named card: [" + cardName + ']'); + game.informPlayers(sourceObject.getLogName() + ", chosen name: [" + cardName + ']'); } game.getState().setValue(source.getSourceId().toString() + INFO_KEY, cardName); if (sourceObject instanceof Permanent) { - ((Permanent) sourceObject).addInfo(INFO_KEY, CardUtil.addToolTipMarkTags("Named card: " + cardName), game); + ((Permanent) sourceObject).addInfo(INFO_KEY, CardUtil.addToolTipMarkTags("Chosen name: " + cardName), game); } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index da0131ace9..d75fda572e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -122,8 +122,12 @@ public class CopyEffect extends ContinuousEffectImpl { permanent.addAbility(ability, getSourceId(), game, false); // no new Id so consumed replacement effects are known while new continuousEffects.apply happen. } } - permanent.getPower().setValue(copyFromObject.getPower().getValue()); - permanent.getToughness().setValue(copyFromObject.getToughness().getValue()); + + // Primal Clay example: + // If a creature that’s already on the battlefield becomes a copy of this creature, it copies the power, toughness, + // and abilities that were chosen for this creature as it entered the battlefield. (2018-03-16) + permanent.getPower().setValue(copyFromObject.getPower().getBaseValueModified()); + permanent.getToughness().setValue(copyFromObject.getToughness().getBaseValueModified()); if (copyFromObject instanceof Permanent) { Permanent targetPermanent = (Permanent) copyFromObject; permanent.setTransformed(targetPermanent.isTransformed()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToAttachedEffect.java index 520f3a8a6f..d696229bc9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToAttachedEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -70,8 +69,8 @@ public class PreventDamageToAttachedEffect extends PreventionEffectImpl { } sb.append("damage to "); sb.append(attachmentType.verb()); - sb.append("creature, prevent ").append(amountToPrevent);; - sb.append("of that damage"); + sb.append(" creature, prevent ").append(amountToPrevent);; + sb.append(" of that damage"); } return sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/RecruiterEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RecruiterEffect.java index e7723c6d08..1327e85549 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RecruiterEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RecruiterEffect.java @@ -41,7 +41,7 @@ public class RecruiterEffect extends OneShotEffect { if (controller != null) { TargetCardInLibrary targetCards = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); Cards cards = new CardsImpl(); - if (controller.searchLibrary(targetCards, game)) { + if (controller.searchLibrary(targetCards, source, game)) { cards.addAll(targetCards.getTargets()); } controller.revealCards(staticText, cards, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReplaceOpponentCardsInHandWithSelectedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReplaceOpponentCardsInHandWithSelectedEffect.java index 5635316fee..af77e9b1c3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReplaceOpponentCardsInHandWithSelectedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReplaceOpponentCardsInHandWithSelectedEffect.java @@ -44,7 +44,7 @@ public class ReplaceOpponentCardsInHandWithSelectedEffect extends OneShotEffect TargetCardInLibrary target = new TargetCardInLibrary(searchLibraryForNum, searchLibraryForNum, new FilterCard()); - controller.searchLibrary(target, game, targetOpponent.getId()); + controller.searchLibrary(target, source, game, targetOpponent.getId()); for (UUID cardId : target.getTargets()) { Card targetCard = game.getCard(cardId); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToHandTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToHandTargetEffect.java index c7b01a71f2..46fdf8631c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToHandTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToHandTargetEffect.java @@ -1,9 +1,10 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.Outcome; import mage.constants.Zone; @@ -34,8 +35,15 @@ public class ReturnFromGraveyardToHandTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - return controller.moveCards(new CardsImpl(getTargetPointer().getTargets(game, source)), Zone.HAND, source, game); + if (controller == null) { + return false; + } + Cards cardsInGraveyard = new CardsImpl(getTargetPointer().getTargets(game, source)); + for (Card card : cardsInGraveyard.getCards(game)) { + if (card != null + && game.getState().getZone(card.getId()) == Zone.GRAVEYARD) { + controller.moveCards(card, Zone.HAND, source, game); //verify the target card is still in the graveyard + } } return false; } 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 2df6a416e6..b47fd8d748 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 @@ -33,7 +33,7 @@ public class CantAttackYouAllEffect extends RestrictionEffect { this.alsoPlaneswalker = alsoPlaneswalker; staticText = filterAttacker.getMessage() + " can't attack you" + (alsoPlaneswalker ? " or a planeswalker you control" : "") - + (duration == Duration.UntilYourNextTurn ? " until your next turn" : ""); + + (duration == Duration.UntilYourNextTurn || duration == Duration.UntilEndOfYourNextTurn ? " " + duration.toString() : ""); } CantAttackYouAllEffect(final CantAttackYouAllEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureAllEffect.java index 54d96c75d1..07892612d6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureAllEffect.java @@ -1,19 +1,13 @@ - package mage.abilities.effects.common.continuous; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.ContinuousEffectImpl; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.Token; import java.util.HashSet; @@ -21,7 +15,6 @@ import java.util.Set; /** * @author LevelX2 - * */ public class BecomesCreatureAllEffect extends ContinuousEffectImpl { @@ -29,13 +22,19 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl { protected String theyAreStillType; private final FilterPermanent filter; private boolean loseColor = true; + protected boolean loseName = false; public BecomesCreatureAllEffect(Token token, String theyAreStillType, FilterPermanent filter, Duration duration, boolean loseColor) { + this(token, theyAreStillType, filter, duration, loseColor, false); + } + + public BecomesCreatureAllEffect(Token token, String theyAreStillType, FilterPermanent filter, Duration duration, boolean loseColor, boolean loseName) { super(duration, Outcome.BecomeCreature); this.token = token; this.theyAreStillType = theyAreStillType; this.filter = filter; this.loseColor = loseColor; + this.loseName = loseName; } public BecomesCreatureAllEffect(final BecomesCreatureAllEffect effect) { @@ -44,6 +43,7 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl { this.theyAreStillType = effect.theyAreStillType; this.filter = effect.filter.copy(); this.loseColor = effect.loseColor; + this.loseName = effect.loseName; } @Override @@ -65,30 +65,47 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl { public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Set affectedPermanents = new HashSet<>(); if (this.affectedObjectsSet) { - for(MageObjectReference ref : affectedObjectList) { + for (MageObjectReference ref : affectedObjectList) { affectedPermanents.add(ref.getPermanent(game)); } } else { affectedPermanents = new HashSet<>(game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)); } - for(Permanent permanent : affectedPermanents) { + for (Permanent permanent : affectedPermanents) { if (permanent != null) { switch (layer) { + case TextChangingEffects_3: + if (sublayer == SubLayer.NA) { + if (loseName) { + permanent.setName(token.getName()); + } + } + break; + case TypeChangingEffects_4: if (sublayer == SubLayer.NA) { - if (!token.getCardType().isEmpty()) { - for (CardType t : token.getCardType()) { - if (!permanent.getCardType().contains(t)) { - permanent.addCardType(t); + if (theyAreStillType != null) { + permanent.getSubtype(game).retainAll(SubType.getLandTypes()); + permanent.getSubtype(game).addAll(token.getSubtype(game)); + } else { + for (SubType t : token.getSubtype(game)) { + if (!permanent.hasSubtype(t, game)) { + permanent.getSubtype(game).add(t); } } } - if (theyAreStillType == null) { - permanent.getSubtype(game).clear(); + + for (SuperType t : token.getSuperType()) { + if (!permanent.getSuperType().contains(t)) { + permanent.addSuperType(t); + } } - if (!token.getSubtype(game).isEmpty()) { - permanent.getSubtype(game).addAll(token.getSubtype(game)); + + for (CardType t : token.getCardType()) { + if (!permanent.getCardType().contains(t)) { + permanent.addCardType(t); + } } } break; @@ -141,7 +158,11 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl { @Override public boolean hasLayer(Layer layer) { - return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4; + return layer == Layer.PTChangingEffects_7 + || layer == Layer.AbilityAddingRemovingEffects_6 + || layer == Layer.ColorChangingEffects_5 + || layer == Layer.TypeChangingEffects_4 + || layer == Layer.TextChangingEffects_3; } @Override 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 b54244b30a..ae2f629c6c 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 @@ -20,25 +20,34 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { protected Token token; protected boolean loseAllAbilities; protected boolean addStillALandText; + protected boolean loseName; + + + public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration) { + this(token, loseAllAbilities, stillALand, duration, false); + } /** * @param token * @param loseAllAbilities loses all subtypes and colors * @param stillALand add rule text, "it's still a land" + * @param loseName permanent lose name and get's it from token * @param duration */ - public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration) { + public BecomesCreatureTargetEffect(Token token, boolean loseAllAbilities, boolean stillALand, Duration duration, boolean loseName) { super(duration, Outcome.BecomeCreature); this.token = token; this.loseAllAbilities = loseAllAbilities; this.addStillALandText = stillALand; + this.loseName = loseName; } public BecomesCreatureTargetEffect(final BecomesCreatureTargetEffect effect) { super(effect); - token = effect.token.copy(); + this.token = effect.token.copy(); this.loseAllAbilities = effect.loseAllAbilities; this.addStillALandText = effect.addStillALandText; + this.loseName = effect.loseName; } @Override @@ -53,30 +62,41 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { switch (layer) { + case TextChangingEffects_3: + if (sublayer == SubLayer.NA) { + if (loseName) { + permanent.setName(token.getName()); + } + } + break; + case TypeChangingEffects_4: if (sublayer == SubLayer.NA) { if (loseAllAbilities) { permanent.getSubtype(game).retainAll(SubType.getLandTypes()); permanent.getSubtype(game).addAll(token.getSubtype(game)); } else { - if (!token.getSubtype(game).isEmpty()) { - for (SubType subtype : token.getSubtype(game)) { - if (!permanent.hasSubtype(subtype, game)) { - permanent.getSubtype(game).add(subtype); - } + for (SubType t : token.getSubtype(game)) { + if (!permanent.hasSubtype(t, game)) { + permanent.getSubtype(game).add(t); } - } } - if (!token.getCardType().isEmpty()) { - for (CardType t : token.getCardType()) { - if (!permanent.getCardType().contains(t)) { - permanent.addCardType(t); - } + + for (SuperType t : token.getSuperType()) { + if (!permanent.getSuperType().contains(t)) { + permanent.addSuperType(t); + } + } + + for (CardType t : token.getCardType()) { + if (!permanent.getCardType().contains(t)) { + permanent.addCardType(t); } } } break; + case ColorChangingEffects_5: if (sublayer == SubLayer.NA) { if (loseAllAbilities) { @@ -91,6 +111,7 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { } } break; + case AbilityAddingRemovingEffects_6: if (loseAllAbilities) { permanent.removeAllAbilities(source.getSourceId(), game); @@ -125,7 +146,11 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { @Override public boolean hasLayer(Layer layer) { - return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4; + return layer == Layer.PTChangingEffects_7 + || layer == Layer.AbilityAddingRemovingEffects_6 + || layer == Layer.ColorChangingEffects_5 + || layer == Layer.TypeChangingEffects_4 + || layer == Layer.TextChangingEffects_3; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java new file mode 100644 index 0000000000..52a614f100 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java @@ -0,0 +1,50 @@ +package mage.abilities.effects.common.continuous; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl { + + public LookAtTopCardOfLibraryAnyTimeEffect() { + super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + staticText = "You may look at the top card of your library any time."; + } + + private LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return true; + } + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard == null) { + return true; + } + MageObject obj = source.getSourceObject(game); + if (obj == null) { + return true; + } + controller.lookAtCards("Top card of " + obj.getIdName() + " controller's library", topCard, game); + return true; + } + + @Override + public LookAtTopCardOfLibraryAnyTimeEffect copy() { + return new LookAtTopCardOfLibraryAnyTimeEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/UntapAllDuringEachOtherPlayersUntapStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/UntapAllDuringEachOtherPlayersUntapStepEffect.java index 0b8c11b397..bc2b0c8935 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/UntapAllDuringEachOtherPlayersUntapStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/UntapAllDuringEachOtherPlayersUntapStepEffect.java @@ -33,13 +33,13 @@ public class UntapAllDuringEachOtherPlayersUntapStepEffect extends ContinuousEff @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Boolean applied = (Boolean) game.getState().getValue(source.getSourceId() + "applied"); - if (applied == null) { - applied = Boolean.FALSE; - } - if (!applied && layer == Layer.RulesEffects) { - if (!source.isControlledBy(game.getActivePlayerId()) && game.getStep().getType() == PhaseStep.UNTAP) { - game.getState().setValue(source.getSourceId() + "applied", true); + if (layer == Layer.RulesEffects && game.getStep().getType() == PhaseStep.UNTAP && !source.isControlledBy(game.getActivePlayerId())) { + Integer appliedTurn = (Integer) game.getState().getValue(source.getSourceId() + "appliedTurn"); + if (appliedTurn == null) { + appliedTurn = 0; + } + if (appliedTurn < game.getTurnNum()) { + game.getState().setValue(source.getSourceId() + "appliedTurn", game.getTurnNum()); for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) { boolean untap = true; for (RestrictionEffect effect : game.getContinuousEffects().getApplicableRestrictionEffects(permanent, game).keySet()) { @@ -50,10 +50,6 @@ public class UntapAllDuringEachOtherPlayersUntapStepEffect extends ContinuousEff } } } - } else if (applied && layer == Layer.RulesEffects) { - if (game.getStep().getType() == PhaseStep.END_TURN) { - game.getState().setValue(source.getSourceId() + "applied", false); - } } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java index 32a056f599..4e62811543 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common.cost; import mage.abilities.Ability; @@ -13,14 +12,17 @@ import mage.game.Game; import mage.util.CardUtil; /** - * * @author LevelX2 */ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl { private final int amount; private ManaCosts manaCostsToReduce = null; - private final Condition condition; + private Condition condition; + + public SpellCostReductionSourceEffect(ManaCosts manaCostsToReduce) { + this(manaCostsToReduce, null); + } public SpellCostReductionSourceEffect(ManaCosts manaCostsToReduce, Condition condition) { super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); @@ -33,19 +35,28 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl { for (String manaSymbol : manaCostsToReduce.getSymbols()) { sb.append(manaSymbol); } - sb.append(" less to if ").append(this.condition.toString()); + sb.append(" less"); + if (this.condition != null) { + sb.append(" to if ").append(this.condition.toString()); + } + this.staticText = sb.toString(); } + public SpellCostReductionSourceEffect(int amount) { + this(amount, null); + } + public SpellCostReductionSourceEffect(int amount, Condition condition) { super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); this.amount = amount; this.condition = condition; StringBuilder sb = new StringBuilder(); - sb.append("{this} costs {") - .append(amount).append("} less to cast ") - .append((this.condition.toString().startsWith("if ") ? "" : "if ")) - .append(this.condition.toString()); + sb.append("{this} costs {").append(amount).append("} less to cast"); + if (this.condition != null) { + sb.append(" ").append(this.condition.toString().startsWith("if ") ? "" : "if "); + sb.append(this.condition.toString()); + } this.staticText = sb.toString(); } @@ -69,7 +80,7 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) { - return condition.apply(game, source); + return condition == null || condition.apply(game, source); } return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/ProliferateEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/ProliferateEffect.java index 512b646341..e82e3c94de 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/ProliferateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/ProliferateEffect.java @@ -1,23 +1,20 @@ - package mage.abilities.effects.common.counter; -import java.io.Serializable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.choices.Choice; -import mage.choices.ChoiceImpl; import mage.constants.Outcome; import mage.counters.Counter; +import mage.filter.common.FilterPermanentOrPlayerWithCounter; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetPermanentOrPlayerWithCounter; +import mage.target.common.TargetPermanentOrPlayer; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; /** * @author nantuko @@ -25,8 +22,19 @@ import mage.target.common.TargetPermanentOrPlayerWithCounter; public class ProliferateEffect extends OneShotEffect { public ProliferateEffect() { + this("", true); + } + + public ProliferateEffect(boolean showAbilityHint) { + this("", showAbilityHint); + } + + public ProliferateEffect(String afterText, boolean showAbilityHint) { super(Outcome.Benefit); - staticText = "proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)"; + staticText = "proliferate" + afterText; + if (showAbilityHint) { + staticText += ". (You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)"; + } } public ProliferateEffect(ProliferateEffect effect) { @@ -36,10 +44,11 @@ public class ProliferateEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); + Counter newCounter = null; if (controller == null) { return false; } - Target target = new TargetPermanentOrPlayerWithCounter(0, Integer.MAX_VALUE, true); + Target target = new TargetPermanentOrPlayer(0, Integer.MAX_VALUE, new FilterPermanentOrPlayerWithCounter(), true); Map options = new HashMap<>(); options.put("UI.right.btn.text", "Done"); controller.choose(Outcome.Benefit, target, source.getSourceId(), game, options); @@ -49,18 +58,30 @@ public class ProliferateEffect extends OneShotEffect { if (permanent != null) { if (!permanent.getCounters(game).isEmpty()) { for (Counter counter : permanent.getCounters(game).values()) { - Counter newCounter = new Counter(counter.getName()); + newCounter = new Counter(counter.getName()); permanent.addCounters(newCounter, source, game); } + if (newCounter != null) { + game.informPlayers(permanent.getName() + + " had 1 " + + newCounter.getName() + + " counter added to it."); + } } } else { Player player = game.getPlayer(chosen); if (player != null) { if (!player.getCounters().isEmpty()) { for (Counter counter : player.getCounters().values()) { - Counter newCounter = new Counter(counter.getName()); + newCounter = new Counter(counter.getName()); player.addCounters(newCounter, game); } + if (newCounter != null) { + game.informPlayers(player.getName() + + " had 1 " + + newCounter.getName() + + " counter added to them."); + } } } } 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 ad8a25bbde..e658515c4c 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 @@ -106,7 +106,7 @@ public class DiscardCardYouChooseTargetEffect extends OneShotEffect { Cards revealedCards = new CardsImpl(); numberToReveal = Math.min(player.getHand().size(), numberToReveal); if (player.getHand().size() > numberToReveal) { - TargetCardInHand chosenCards = new TargetCardInHand(numberToReveal, numberToReveal, new FilterCard("card in " + player.getLogName() + "'s hand")); + TargetCardInHand chosenCards = new TargetCardInHand(numberToReveal, numberToReveal, new FilterCard("card in " + player.getName() + "'s hand")); chosenCards.setNotTarget(true); if (chosenCards.canChoose(player.getId(), game) && player.chooseTarget(Outcome.Discard, player.getHand(), chosenCards, source, game)) { if (!chosenCards.getTargets().isEmpty()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java index 4da00d454c..e23b92b1dc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutInHandEffect.java @@ -59,7 +59,7 @@ public class SearchLibraryGraveyardPutInHandEffect extends OneShotEffect { if (forceToSearchBoth || controller.chooseUse(outcome, "Search your library for a card named " + filter.getMessage() + '?', source, game)) { TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); target.clearChosen(); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { cardFound = game.getCard(target.getFirstTarget()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutOntoBattlefieldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutOntoBattlefieldEffect.java new file mode 100644 index 0000000000..627244f47b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardPutOntoBattlefieldEffect.java @@ -0,0 +1,91 @@ +package mage.abilities.effects.common.search; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +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.TargetCard; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author Styxo + */ +public class SearchLibraryGraveyardPutOntoBattlefieldEffect extends OneShotEffect { + + private FilterCard filter; + private boolean forceToSearchBoth; + + public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter) { + this(filter, false); + } + + public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter, boolean forceToSearchBoth) { + this(filter, forceToSearchBoth, false); + } + + public SearchLibraryGraveyardPutOntoBattlefieldEffect(FilterCard filter, boolean forceToSearchBoth, boolean youMay) { + super(Outcome.Benefit); + this.filter = filter; + this.forceToSearchBoth = forceToSearchBoth; + staticText = (youMay ? "You may" : "") + " search your library and" + (forceToSearchBoth ? "" : "/or") + " graveyard for a card named " + filter.getMessage() + + ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle your library" : "If you search your library this way, shuffle it"); + } + + public SearchLibraryGraveyardPutOntoBattlefieldEffect(final SearchLibraryGraveyardPutOntoBattlefieldEffect effect) { + super(effect); + this.filter = effect.filter; + this.forceToSearchBoth = effect.forceToSearchBoth; + + } + + @Override + public SearchLibraryGraveyardPutOntoBattlefieldEffect copy() { + return new SearchLibraryGraveyardPutOntoBattlefieldEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + Card cardFound = null; + if (controller != null && sourceObject != null) { + if (forceToSearchBoth || controller.chooseUse(outcome, "Search your library for a card named " + filter.getMessage() + '?', source, game)) { + TargetCardInLibrary target = new TargetCardInLibrary(0, 1, filter); + target.clearChosen(); + if (controller.searchLibrary(target, source, game)) { + if (!target.getTargets().isEmpty()) { + cardFound = game.getCard(target.getFirstTarget()); + } + } + controller.shuffleLibrary(source, game); + } + + if (cardFound == null && controller.chooseUse(outcome, "Search your graveyard for a card named " + filter.getMessage() + '?', source, game)) { + TargetCard target = new TargetCard(0, 1, Zone.GRAVEYARD, filter); + target.clearChosen(); + if (controller.choose(outcome, controller.getGraveyard(), target, game)) { + if (!target.getTargets().isEmpty()) { + cardFound = game.getCard(target.getFirstTarget()); + } + } + } + + if (cardFound != null) { + controller.revealCards(sourceObject.getIdName(), new CardsImpl(cardFound), game); + controller.moveCards(cardFound, Zone.BATTLEFIELD, source, game); + } + + return true; + } + + return false; + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardWithLessCMCPutIntoPlay.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardWithLessCMCPutIntoPlay.java new file mode 100644 index 0000000000..09754429ba --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryGraveyardWithLessCMCPutIntoPlay.java @@ -0,0 +1,87 @@ +package mage.abilities.effects.common.search; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.constants.ComparisonType; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author antoni-g + */ +public class SearchLibraryGraveyardWithLessCMCPutIntoPlay extends OneShotEffect { + + private final FilterCard filter; + + public SearchLibraryGraveyardWithLessCMCPutIntoPlay() { + this(new FilterCard()); + } + + public SearchLibraryGraveyardWithLessCMCPutIntoPlay(FilterCard filter) { + super(Outcome.PutCreatureInPlay); + this.filter = filter; + staticText = "Search your library or graveyard for a " + filter.getMessage() + " with converted mana cost X or less, put it onto the battlefield, then shuffle your library"; + } + + public SearchLibraryGraveyardWithLessCMCPutIntoPlay(final SearchLibraryGraveyardWithLessCMCPutIntoPlay effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public SearchLibraryGraveyardWithLessCMCPutIntoPlay copy() { + return new SearchLibraryGraveyardWithLessCMCPutIntoPlay(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + Card cardFound = null; + if (controller != null && sourceObject != null) { + // create x cost filter + FilterCard advancedFilter = filter.copy(); // never change static objects so copy the object here before + advancedFilter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1)); + + if (controller.chooseUse(outcome, "Search your library for a " + filter.getMessage() + " with CMC X or less" + '?', source, game)) { + TargetCardInLibrary target = new TargetCardInLibrary(advancedFilter); + target.clearChosen(); + if (controller.searchLibrary(target, source, game)) { + if (!target.getTargets().isEmpty()) { + cardFound = game.getCard(target.getFirstTarget()); + } + } + controller.shuffleLibrary(source, game); + } + + if (cardFound == null && controller.chooseUse(outcome, "Search your graveyard for a " + filter.getMessage() + " with CMC X or less" + '?', source, game)) { + TargetCard target = new TargetCard(0, 1, Zone.GRAVEYARD, advancedFilter); + target.clearChosen(); + if (controller.choose(outcome, controller.getGraveyard(), target, game)) { + if (!target.getTargets().isEmpty()) { + cardFound = game.getCard(target.getFirstTarget()); + } + } + } + + if (cardFound != null) { + controller.revealCards(sourceObject.getIdName(), new CardsImpl(cardFound), game); + controller.moveCards(cardFound, Zone.BATTLEFIELD, source, game); + } + + return true; + } + return false; + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandEffect.java index 23a1f43d8b..788c3e3754 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandEffect.java @@ -63,7 +63,7 @@ public class SearchLibraryPutInHandEffect extends SearchEffect { return false; } target.clearChosen(); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); for (UUID cardId : target.getTargets()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java index 87e8ed12f5..86bd7d7337 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInHandOrOnBattlefieldEffect.java @@ -66,7 +66,7 @@ public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect { return false; } target.clearChosen(); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards cards = new CardsImpl(); boolean askToPutOntoBf = false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java index c519f1736e..940931dfac 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java @@ -62,7 +62,7 @@ public class SearchLibraryPutInPlayEffect extends SearchEffect { if (player == null) { return false; } - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, tapped, false, false, null); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayTargetPlayerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayTargetPlayerEffect.java index 507223d1f6..e2877c9fc6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayTargetPlayerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInPlayTargetPlayerEffect.java @@ -70,7 +70,7 @@ public class SearchLibraryPutInPlayTargetPlayerEffect extends SearchEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - if (player.searchLibrary(target, game)) { + if (player.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, tapped, false, ownerIsController, null); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOnLibraryEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOnLibraryEffect.java index 0766baf14e..d9923a55b0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOnLibraryEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutOnLibraryEffect.java @@ -50,7 +50,7 @@ public class SearchLibraryPutOnLibraryEffect extends SearchEffect { if (controller == null || sourceObject == null) { return false; } - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { Cards foundCards = new CardsImpl(target.getTargets()); if (reveal && !foundCards.isEmpty()) { controller.revealCards(sourceObject.getIdName(), foundCards, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryWithLessCMCPutInPlayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryWithLessCMCPutInPlayEffect.java index 61d5e9e2cf..c116004e5e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryWithLessCMCPutInPlayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryWithLessCMCPutInPlayEffect.java @@ -43,7 +43,7 @@ public class SearchLibraryWithLessCMCPutInPlayEffect extends OneShotEffect { FilterCard advancedFilter = filter.copy(); // never change static objects so copy the object here before advancedFilter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, source.getManaCostsToPay().getX() + 1)); TargetCardInLibrary target = new TargetCardInLibrary(advancedFilter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AdaptEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AdaptEffect.java index ed29b59aea..43a81809f3 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/AdaptEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/AdaptEffect.java @@ -1,8 +1,10 @@ package mage.abilities.effects.keyword; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; @@ -19,10 +21,10 @@ public class AdaptEffect extends OneShotEffect { public AdaptEffect(int adaptNumber) { super(Outcome.BoostCreature); this.adaptNumber = adaptNumber; - staticText = "Adapt " + adaptNumber + - " (If this creature has no +1/+1 counters on it, put " + - CardUtil.numberToText(adaptNumber) + " +1/+1 counter" + - (adaptNumber > 1 ? "s" : "") + " on it.)"; + staticText = "Adapt " + adaptNumber + + " (If this creature has no +1/+1 counters on it, put " + + CardUtil.numberToText(adaptNumber) + " +1/+1 counter" + + (adaptNumber > 1 ? "s" : "") + " on it.)"; } private AdaptEffect(final AdaptEffect effect) { @@ -37,7 +39,15 @@ public class AdaptEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); + // Verify source object did not change zone and is on the battlefield + MageObject sourceObject = source.getSourceObjectIfItStillExists(game); + if (sourceObject == null) { + if (game.getState().getZone(source.getSourceId()).equals(Zone.BATTLEFIELD) + && source.getSourceObjectZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) { + sourceObject = game.getPermanent(source.getSourceId()); + } + } + Permanent permanent = ((Permanent) sourceObject); if (permanent == null) { return false; } @@ -48,7 +58,8 @@ public class AdaptEffect extends OneShotEffect { if (game.replaceEvent(event)) { return false; } - if (permanent.getCounters(game).getCount(CounterType.P1P1) == 0 || event.getFlag()) { + if (permanent.getCounters(game).getCount(CounterType.P1P1) == 0 + || event.getFlag()) { permanent.addCounters(CounterType.P1P1.createInstance(event.getAmount()), source, game); } return true; diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java index b5a8160427..ff46aaa66b 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java @@ -38,7 +38,7 @@ public class AmassEffect extends OneShotEffect { public AmassEffect(int amassNumber) { this(new StaticValue(amassNumber)); staticText = "amass " + amassNumber + ". (Put " + CardUtil.numberToText(amassNumber) - + " +1/+1 counter " + (amassNumber > 1 ? "s" : "") + + " +1/+1 counter" + (amassNumber > 1 ? "s " : " ") + "on an Army you control. If you don’t control one, " + "create a 0/0 black Zombie Army creature token first.)"; } @@ -68,7 +68,7 @@ public class AmassEffect extends OneShotEffect { if (player == null) { return false; } - if (!game.getBattlefield().contains(filter, 1, game)) { + if (!game.getBattlefield().contains(filter, source.getControllerId(), 1, game)) { new CreateTokenEffect(new ZombieArmyToken()).apply(game, source); } Target target = new TargetPermanent(filter); diff --git a/Mage/src/main/java/mage/abilities/hint/common/PermanentsYouControlHint.java b/Mage/src/main/java/mage/abilities/hint/common/PermanentsYouControlHint.java new file mode 100644 index 0000000000..cf47e835cd --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/PermanentsYouControlHint.java @@ -0,0 +1,26 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.PermanentsYouControlCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.game.Game; + +/** + * @author JayDi85 + */ +public enum PermanentsYouControlHint implements Hint { + + instance; + private static final Hint hint = new ValueHint("Permanents you control", PermanentsYouControlCount.instance); + + @Override + public String getText(Game game, Ability ability) { + return hint.getText(game, ability); + } + + @Override + public Hint copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/DelveAbility.java b/Mage/src/main/java/mage/abilities/keyword/DelveAbility.java index 68055acec9..2855262cdb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DelveAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DelveAbility.java @@ -1,7 +1,5 @@ - package mage.abilities.keyword; -import java.util.List; import mage.Mana; import mage.abilities.Ability; import mage.abilities.SpecialAction; @@ -24,6 +22,8 @@ import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; import mage.util.CardUtil; +import java.util.List; + /** * 702.65. Delve 702.65a Delve is a static ability that functions while the * spell with delve is on the stack. “Delve” means “For each generic mana in @@ -31,7 +31,7 @@ import mage.util.CardUtil; * pay that mana.” The delve ability isn't an additional or alternative cost and * applies only after the total cost of the spell with delve is determined. * 702.65b Multiple instances of delve on the same spell are redundant. - * + *

* The rules for delve have changed slightly since it was last in an expansion. * Previously, delve reduced the cost to cast a spell. Under the current rules, * you exile cards from your graveyard at the same time you pay the spell's @@ -45,7 +45,7 @@ import mage.util.CardUtil; * it can be used in conjunction with alternative costs. * * @author LevelX2 - * + *

* TODO: Change card exiling to a way to pay mana costs, now it's maybe not * possible to pay costs from effects that increase the mana costs. */ @@ -83,7 +83,8 @@ public class DelveAbility extends SimpleStaticAbility implements AlternateManaPa unpaidAmount = 1; } specialAction.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard( - 0, Math.min(controller.getGraveyard().size(), unpaidAmount), new FilterCard(), true))); + 0, Math.min(controller.getGraveyard().size(), unpaidAmount), + new FilterCard("cards to exile for delve's pay from your graveyard"), true))); if (specialAction.canActivate(source.getControllerId(), game).canActivate()) { game.getState().getSpecialActions().add(specialAction); } diff --git a/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java b/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java index 43bea447a7..a9f9a82ec0 100644 --- a/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/PartnerWithAbility.java @@ -116,7 +116,7 @@ class PartnersWithSearchEffect extends OneShotEffect { filter.add(new NamePredicate(partnerName)); TargetCardInLibrary target = new TargetCardInLibrary(filter); if (player.chooseUse(Outcome.Benefit, "Search your library for a card named " + partnerName + " and put it into your hand?", source, game)) { - player.searchLibrary(target, game); + player.searchLibrary(target, source, game); for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().getCard(cardId, game); if (card != null) { diff --git a/Mage/src/main/java/mage/abilities/keyword/TransmuteAbility.java b/Mage/src/main/java/mage/abilities/keyword/TransmuteAbility.java index f0143e4f0f..7a4c9770b2 100644 --- a/Mage/src/main/java/mage/abilities/keyword/TransmuteAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/TransmuteAbility.java @@ -80,7 +80,7 @@ class TransmuteEffect extends OneShotEffect { FilterCard filter = new FilterCard("card with converted mana cost " + sourceObject.getConvertedManaCost()); filter.add(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, sourceObject.getConvertedManaCost())); TargetCardInLibrary target = new TargetCardInLibrary(1, filter); - if (controller.searchLibrary(target, game)) { + if (controller.searchLibrary(target, source, game)) { if (!target.getTargets().isEmpty()) { Cards revealed = new CardsImpl(target.getTargets()); controller.revealCards(sourceObject.getIdName(), revealed, game); diff --git a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java index 31e6d3424e..ef3dad3208 100644 --- a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.keyword; import mage.abilities.Ability; @@ -19,19 +18,17 @@ import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; /** - * * @author BetaSteward_at_googlemail.com - * - * + *

+ *

* 702.82. Unearth - * + *

* 702.82a Unearth is an activated ability that functions while the card with * unearth is in a graveyard. "Unearth [cost]" means "[Cost]: Return this card * from your graveyard to the battlefield. It gains haste. Exile it at the * beginning of the next end step. If it would leave the battlefield, exile it * instead of putting it anywhere else. Activate this ability only any time you * could cast a sorcery." - * */ public class UnearthAbility extends ActivatedAbilityImpl { @@ -111,7 +108,7 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return EventType.ZONE_CHANGE == event.getType(); + return event.getType() == EventType.ZONE_CHANGE; } @Override diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index ae22f9da2a..317bec4add 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -690,27 +690,33 @@ public abstract class CardImpl extends MageObjectImpl implements Card { sourceId = source.getSourceId(); } } - GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, sourceId, getControllerOrOwner(), counter.getName(), counter.getCount()); - countersEvent.setAppliedEffects(appliedEffects); - countersEvent.setFlag(isEffect); - if (!game.replaceEvent(countersEvent)) { - int amount = countersEvent.getAmount(); + GameEvent addingAllEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, sourceId, getControllerOrOwner(), counter.getName(), counter.getCount()); + addingAllEvent.setAppliedEffects(appliedEffects); + addingAllEvent.setFlag(isEffect); + if (!game.replaceEvent(addingAllEvent)) { + int amount = addingAllEvent.getAmount(); + boolean isEffectFlag = addingAllEvent.getFlag(); int finalAmount = amount; for (int i = 0; i < amount; i++) { Counter eventCounter = counter.copy(); eventCounter.remove(eventCounter.getCount() - 1); - GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1); - event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { + GameEvent addingOneEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1); + addingOneEvent.setAppliedEffects(appliedEffects); + addingOneEvent.setFlag(isEffectFlag); + if (!game.replaceEvent(addingOneEvent)) { getCounters(game).addCounter(eventCounter); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1)); + GameEvent addedOneEvent = GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), 1); + addedOneEvent.setFlag(addingOneEvent.getFlag()); + game.fireEvent(addedOneEvent); } else { finalAmount--; returnCode = false; } } if (finalAmount > 0) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERS_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), amount)); + GameEvent addedAllEvent = GameEvent.getEvent(GameEvent.EventType.COUNTERS_ADDED, objectId, sourceId, getControllerOrOwner(), counter.getName(), amount); + addedAllEvent.setFlag(isEffectFlag); + game.fireEvent(addedAllEvent); } } else { returnCode = false; @@ -720,6 +726,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public void removeCounters(String name, int amount, Game game) { + int finalAmount = 0; for (int i = 0; i < amount; i++) { if (!getCounters(game).removeCounter(name, 1)) { break; @@ -727,7 +734,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card { GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, getControllerOrOwner()); event.setData(name); game.fireEvent(event); + finalAmount++; } + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, getControllerOrOwner()); + event.setData(name); + event.setAmount(finalAmount); + game.fireEvent(event); } @Override diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index d7b83ed533..ba8ac8073f 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -258,7 +258,7 @@ public abstract class ExpansionSet implements Serializable { return booster.stream().anyMatch(card -> card.isLegendary() && card.isCreature()); } if (needsPlaneswalker) { - return booster.stream().anyMatch(card -> card.isPlaneswalker()); + return booster.stream().filter(card -> card.isPlaneswalker()).count() == 1; } // TODO: add partner check diff --git a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporter.java b/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporter.java index 220637ecff..68073ae2cc 100644 --- a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporter.java +++ b/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporter.java @@ -1,5 +1,6 @@ package mage.cards.decks.exporter; +import com.google.common.collect.ImmutableMap; import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; import mage.cards.decks.DeckFileFilter; @@ -13,9 +14,11 @@ import java.util.*; */ public class MtgArenaDeckExporter extends DeckExporter { - private final String ext = "mtga"; - private final String description = "MTG Arena's deck format (*.mtga)"; - private final FileFilter fileFilter = new DeckFileFilter(ext, description); + private static final String ext = "mtga"; + private static final String description = "MTG Arena's deck format (*.mtga)"; + private static final FileFilter fileFilter = new DeckFileFilter(ext, description); + + private static final Map SET_CODE_REPLACEMENTS = ImmutableMap.of("DOM", "DAR"); @Override public void writeDeck(PrintWriter out, DeckCardLists deck) { @@ -33,7 +36,9 @@ public class MtgArenaDeckExporter extends DeckExporter { private List prepareCardsList(List sourceCards, Map amount, String prefix) { List res = new ArrayList<>(); for (DeckCardInfo card : sourceCards) { - String name = card.getCardName() + " (" + card.getSetCode().toUpperCase(Locale.ENGLISH) + ") " + card.getCardNum(); + String setCode = card.getSetCode().toUpperCase(Locale.ENGLISH); + setCode = SET_CODE_REPLACEMENTS.getOrDefault(setCode, setCode); + String name = card.getCardName() + " (" + setCode + ") " + card.getCardNum(); String code = prefix + name; int curAmount = amount.getOrDefault(code, 0); if (curAmount == 0) { diff --git a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java b/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java index 773a0d19a3..af7666a45d 100644 --- a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java +++ b/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java @@ -22,6 +22,7 @@ public class MtgArenaDeckExporterTest { deck.getCards().add(new DeckCardInfo("Plains", "2", "RNA", 3)); deck.getCards().add(new DeckCardInfo("Plains", "2", "RNA", 5)); // must combine deck.getCards().add(new DeckCardInfo("Mountain", "3", "RNA", 1)); + deck.getCards().add(new DeckCardInfo("Goblin Chainwhirler", "129", "DOM", 4)); deck.getSideboard().add(new DeckCardInfo("Island", "1", "RNA", 2)); deck.getSideboard().add(new DeckCardInfo("Island", "1", "RNA", 5)); // must combine deck.getSideboard().add(new DeckCardInfo("Mountain", "2", "RNA", 3)); @@ -30,6 +31,7 @@ public class MtgArenaDeckExporterTest { assertEquals("2 Forest (RNA) 1" + System.lineSeparator() + "8 Plains (RNA) 2" + System.lineSeparator() + "1 Mountain (RNA) 3" + System.lineSeparator() + + "4 Goblin Chainwhirler (DAR) 129" + System.lineSeparator() + System.lineSeparator() + "7 Island (RNA) 1" + System.lineSeparator() + "3 Mountain (RNA) 2" + System.lineSeparator(), 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 2e20ded641..b7ba7f8059 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java @@ -34,6 +34,8 @@ public abstract class DeckImporter { return new CodDeckImporter(); } else if (file.toLowerCase(Locale.ENGLISH).endsWith("o8d")) { return new O8dDeckImporter(); + } else if (file.toLowerCase(Locale.ENGLISH).endsWith("draft")) { + return new DraftLogImporter(); } else { return null; } diff --git a/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java new file mode 100644 index 0000000000..5914165f1c --- /dev/null +++ b/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java @@ -0,0 +1,46 @@ +package mage.cards.decks.importer; + +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLists; +import mage.cards.repository.CardCriteria; +import mage.cards.repository.CardInfo; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DraftLogImporter extends PlainTextDeckImporter { + + private static Pattern SET_PATTERN = Pattern.compile("------ (\\p{Alnum}+) ------$"); + private static Pattern PICK_PATTERN = Pattern.compile("--> (.+)$"); + + private String currentSet = null; + + @Override + protected void readLine(String line, DeckCardLists deckList) { + Matcher setMatcher = SET_PATTERN.matcher(line); + if (setMatcher.matches()) { + currentSet = setMatcher.group(1); + return; + } + + Matcher pickMatcher = PICK_PATTERN.matcher(line); + if (pickMatcher.matches()) { + String name = pickMatcher.group(1); + List cards = getCardLookup().lookupCardInfo(new CardCriteria().setCodes(currentSet).name(name)); + CardInfo card = null; + if (!cards.isEmpty()) { + card = cards.get(0); + } else { + card = getCardLookup().lookupCardInfo(name).orElse(null); + } + + if (card != null) { + deckList.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode())); + } else { + sbMessage.append("couldn't find: \"").append(name).append("\"\n"); + } + } + } + +} diff --git a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java index 87eca66277..2941efcd6b 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java @@ -79,8 +79,36 @@ public class TxtDeckImporter extends PlainTextDeckImporter { if (delim < 0) { return; } + String lineNum = line.substring(0, delim).trim(); - String lineName = line.substring(delim).replace("'", "\'").trim(); + if (IGNORE_NAMES.contains(lineNum)) { + return; + } + + // amount + int cardAmount = 0; + boolean haveCardAmout; + try { + cardAmount = Integer.parseInt(lineNum.replaceAll("\\D+", "")); + if ((cardAmount <= 0) || (cardAmount >= 100)) { + sbMessage.append("Invalid number (too small or too big): ").append(lineNum).append(" at line ").append(lineCount).append('\n'); + return; + } + haveCardAmout = true; + } catch (NumberFormatException nfe) { + haveCardAmout = false; + //sbMessage.append("Invalid number: ").append(lineNum).append(" at line ").append(lineCount).append('\n'); + //return; + } + + String lineName; + if (haveCardAmout) { + lineName = line.substring(delim).trim(); + } else { + lineName = line.trim(); + cardAmount = 1; + } + lineName = lineName .replace("&", "//") .replace("Æ", "Ae") @@ -96,33 +124,23 @@ public class TxtDeckImporter extends PlainTextDeckImporter { } lineName = lineName.replaceFirst("(?<=[^/])\\s*/\\s*(?=[^/])", " // "); - if (IGNORE_NAMES.contains(lineName) || IGNORE_NAMES.contains(lineNum)) { + if (IGNORE_NAMES.contains(lineName)) { return; } wasCardLines = true; - try { - int num = Integer.parseInt(lineNum.replaceAll("\\D+", "")); - if ((num < 0) || (num > 100)) { - sbMessage.append("Invalid number (too small or too big): ").append(lineNum).append(" at line ").append(lineCount).append('\n'); - return; - } - - CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true); - if (cardInfo == null) { - sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); - } else { - for (int i = 0; i < num; i++) { - if (!sideboard && !singleLineSideBoard) { - deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); - } else { - deckList.getSideboard().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); - } + CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true); + if (cardInfo == null) { + sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); + } else { + for (int i = 0; i < cardAmount; i++) { + if (!sideboard && !singleLineSideBoard) { + deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); + } else { + deckList.getSideboard().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); } } - } catch (NumberFormatException nfe) { - sbMessage.append("Invalid number: ").append(lineNum).append(" at line ").append(lineCount).append('\n'); } } } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 06a3a894dc..e54d6e4812 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -35,7 +35,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 51; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 220; + private static final long CARD_CONTENT_VERSION = 222; private Dao cardDao; private Set classNames; private RepositoryEventSource eventSource = new RepositoryEventSource(); diff --git a/Mage/src/main/java/mage/choices/ChoiceColor.java b/Mage/src/main/java/mage/choices/ChoiceColor.java index 9847e9fd51..6673b4e804 100644 --- a/Mage/src/main/java/mage/choices/ChoiceColor.java +++ b/Mage/src/main/java/mage/choices/ChoiceColor.java @@ -1,4 +1,3 @@ - package mage.choices; import mage.MageObject; @@ -8,14 +7,13 @@ import mage.ObjectColor; import java.util.ArrayList; /** - * * @author BetaSteward_at_googlemail.com, JayDi85 */ public class ChoiceColor extends ChoiceImpl { - private static final ArrayList colorChoices = getBaseColors(); + private static final ArrayList colorChoices = getBaseColors(); - public static ArrayList getBaseColors(){ + public static ArrayList getBaseColors() { ArrayList arr = new ArrayList<>(); arr.add("Green"); arr.add("Blue"); @@ -33,15 +31,15 @@ public class ChoiceColor extends ChoiceImpl { this(required, "Choose color"); } - public ChoiceColor(boolean required, String chooseMessage){ + public ChoiceColor(boolean required, String chooseMessage) { this(required, chooseMessage, ""); } - public ChoiceColor(boolean required, String chooseMessage, MageObject source){ + public ChoiceColor(boolean required, String chooseMessage, MageObject source) { this(required, chooseMessage, source.getIdName()); } - public ChoiceColor(boolean required, String chooseMessage, String chooseSubMessage){ + public ChoiceColor(boolean required, String chooseMessage, String chooseSubMessage) { super(required); this.choices.addAll(colorChoices); @@ -59,6 +57,10 @@ public class ChoiceColor extends ChoiceImpl { return new ChoiceColor(this); } + public void removeColorFromChoices(String colorName) { + this.choices.remove(colorName); + } + public ObjectColor getColor() { if (choice == null) { return null; diff --git a/Mage/src/main/java/mage/constants/CardType.java b/Mage/src/main/java/mage/constants/CardType.java index d060f5aa04..54fb252ac5 100644 --- a/Mage/src/main/java/mage/constants/CardType.java +++ b/Mage/src/main/java/mage/constants/CardType.java @@ -31,6 +31,16 @@ public enum CardType { return text; } + public static CardType fromString(String value) { + for (CardType ct : CardType.values()) { + if (ct.toString().equals(value)) { + return ct; + } + } + + throw new IllegalArgumentException("Can't find card type enum value: " + value); + } + public boolean isPermanentType() { return permanentType; } diff --git a/Mage/src/main/java/mage/constants/Duration.java b/Mage/src/main/java/mage/constants/Duration.java index 1c462cbb3d..0ec5060ed0 100644 --- a/Mage/src/main/java/mage/constants/Duration.java +++ b/Mage/src/main/java/mage/constants/Duration.java @@ -1,7 +1,6 @@ package mage.constants; /** - * * @author North */ public enum Duration { @@ -12,6 +11,7 @@ public enum Duration { WhileInGraveyard("", false), EndOfTurn("until end of turn", true), UntilYourNextTurn("until your next turn", true), + UntilEndOfYourNextTurn("until the end of your next turn", true), UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true), // supported for continuous layered effects EndOfCombat("until end of combat", true), EndOfStep("until end of phase step", true), diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 7c29322629..00387a9761 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -201,6 +201,7 @@ public enum SubType { KOR("Kor", SubTypeSet.CreatureType), KRAKEN("Kraken", SubTypeSet.CreatureType), // L + LADYOFPROPERETIQUETTE("Lady of Proper Etiquette", SubTypeSet.CreatureType, true), // Unglued LAMIA("Lamia", SubTypeSet.CreatureType), LAMMASU("Lammasu", SubTypeSet.CreatureType), LEECH("Leech", SubTypeSet.CreatureType), @@ -392,6 +393,7 @@ public enum SubType { HUATLI("Huatli", SubTypeSet.PlaneswalkerType), JACE("Jace", SubTypeSet.PlaneswalkerType), KARN("Karn", SubTypeSet.PlaneswalkerType), + KASMINA("Kasmina", SubTypeSet.PlaneswalkerType), KAYA("Kaya", SubTypeSet.PlaneswalkerType), KIORA("Kiora", SubTypeSet.PlaneswalkerType), KOTH("Koth", SubTypeSet.PlaneswalkerType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 57274198a0..1478bd84c5 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -135,6 +135,7 @@ public enum CounterType { VERSE("verse"), VITALITY("vitality"), VORTEX("vortex"), + WAGE("wage"), WINCH("winch"), WIND("wind"), WISH("wish"); @@ -193,6 +194,11 @@ public enum CounterType { } } + @Override + public String toString() { + return name; + } + public static CounterType findByName(String name) { for (CounterType counterType : values()) { if (counterType.getName().equals(name)) { diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index b40ed88702..dba8893667 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -417,6 +417,12 @@ public final class StaticFilters { FILTER_PERMANENT_PLANESWALKER.setLockedFilter(true); } + public static final FilterPlaneswalkerPermanent FILTER_PERMANENT_PLANESWALKERS = new FilterPlaneswalkerPermanent("planeswalkers"); + + static { + FILTER_PERMANENT_PLANESWALKERS.setLockedFilter(true); + } + public static final FilterNonlandPermanent FILTER_PERMANENT_NON_LAND = new FilterNonlandPermanent(); static { @@ -460,6 +466,12 @@ public final class StaticFilters { FILTER_SPELL_NON_CREATURE.setLockedFilter(true); } + public static final FilterSpell FILTER_SPELL_A_NON_CREATURE = (FilterSpell) new FilterSpell("a noncreature spell").add(Predicates.not(new CardTypePredicate(CardType.CREATURE))); + + static { + FILTER_SPELL_A_NON_CREATURE.setLockedFilter(true); + } + public static final FilterSpell FILTER_SPELL = new FilterSpell(); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayerWithCounter.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayerWithCounter.java index 6f5dfa5dba..16c0eb3623 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayerWithCounter.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayerWithCounter.java @@ -1,13 +1,11 @@ - - package mage.filter.common; +import mage.MageItem; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import java.util.UUID; -import mage.MageItem; /** * @author nantuko @@ -28,24 +26,24 @@ public class FilterPermanentOrPlayerWithCounter extends FilterPermanentOrPlayer @Override public boolean match(MageItem o, Game game) { - if (o instanceof Player) { - if (((Player)o).getCounters().isEmpty()) { - return false; - } - } else if (o instanceof Permanent) { - if (((Permanent)o).getCounters(game).isEmpty()) { - return false; + if (super.match(o, game)) { + if (o instanceof Player) { + return !((Player) o).getCounters().isEmpty(); + } else if (o instanceof Permanent) { + return !((Permanent) o).getCounters(game).isEmpty(); } } - return super.match(o, game); + return false; } @Override public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) { - if (o instanceof Player) { - return playerFilter.match((Player) o, sourceId, playerId, game); - } else if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, sourceId, playerId, game); + if (super.match(o, sourceId, playerId, game)) { + if (o instanceof Player) { + return !((Player) o).getCounters().isEmpty(); + } else if (o instanceof Permanent) { + return !((Permanent) o).getCounters(game).isEmpty(); + } } return false; } diff --git a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerPermanent.java b/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerPermanent.java index 0fef222e96..8149b208bf 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerPermanent.java +++ b/Mage/src/main/java/mage/filter/common/FilterPlaneswalkerPermanent.java @@ -3,11 +3,12 @@ package mage.filter.common; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; /** - * * @author BetaSteward_at_googlemail.com */ public class FilterPlaneswalkerPermanent extends FilterPermanent { @@ -16,6 +17,11 @@ public class FilterPlaneswalkerPermanent extends FilterPermanent { this("planeswalker"); } + public FilterPlaneswalkerPermanent(SubType subType) { + this(subType.getDescription() + " planeswalker"); + this.add(new SubtypePredicate(subType)); + } + public FilterPlaneswalkerPermanent(String name) { super(name); this.add(new CardTypePredicate(CardType.PLANESWALKER)); diff --git a/Mage/src/main/java/mage/game/Exile.java b/Mage/src/main/java/mage/game/Exile.java index 304bbe07ec..a8a4474e40 100644 --- a/Mage/src/main/java/mage/game/Exile.java +++ b/Mage/src/main/java/mage/game/Exile.java @@ -1,22 +1,15 @@ - package mage.game; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; -import java.util.stream.Collectors; - import mage.cards.Card; import mage.filter.FilterCard; import mage.util.Copyable; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + /** - * * @author BetaSteward_at_googlemail.com */ public class Exile implements Serializable, Copyable { @@ -114,4 +107,17 @@ public class Exile implements Serializable, Copyable { exile.clear(); } } + + public void cleanupEndOfTurnZones(Game game) { + // moves cards from outdated zone to main exile zone + ExileZone mainZone = getExileZone(PERMANENT); + for (ExileZone zone : exileZones.values()) { + if (zone.isCleanupOnEndTurn()) { + for (Card card : zone.getCards(game)) { + mainZone.add(card); + zone.remove(card); + } + } + } + } } diff --git a/Mage/src/main/java/mage/game/ExileZone.java b/Mage/src/main/java/mage/game/ExileZone.java index 2b9395ee12..451d2bb249 100644 --- a/Mage/src/main/java/mage/game/ExileZone.java +++ b/Mage/src/main/java/mage/game/ExileZone.java @@ -1,13 +1,10 @@ - - package mage.game; -import java.util.UUID; - import mage.cards.CardsImpl; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class ExileZone extends CardsImpl { @@ -15,16 +12,22 @@ public class ExileZone extends CardsImpl { private UUID id; private String name; private boolean hidden; + private boolean cleanupOnEndTurn = false; // moved cards from that zone to default on end of turn (to cleanup exile windows) public ExileZone(UUID id, String name) { this(id, name, false); } public ExileZone(UUID id, String name, boolean hidden) { + this(id, name, false, false); + } + + public ExileZone(UUID id, String name, boolean hidden, boolean cleanupOnEndTurn) { super(); this.id = id; this.name = name; this.hidden = hidden; + this.cleanupOnEndTurn = cleanupOnEndTurn; } public ExileZone(final ExileZone zone) { @@ -32,6 +35,7 @@ public class ExileZone extends CardsImpl { this.id = zone.id; this.name = zone.name; this.hidden = zone.hidden; + this.cleanupOnEndTurn = zone.cleanupOnEndTurn; } public UUID getId() { @@ -46,6 +50,14 @@ public class ExileZone extends CardsImpl { return hidden; } + public boolean isCleanupOnEndTurn() { + return cleanupOnEndTurn; + } + + public void setCleanupOnEndTurn(boolean cleanupOnEndTurn) { + this.cleanupOnEndTurn = cleanupOnEndTurn; + } + @Override public ExileZone copy() { return new ExileZone(this); diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 4772323460..30638c63a1 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -25,7 +25,6 @@ import mage.game.events.GameEvent; import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; -import mage.game.match.Match; import mage.game.match.MatchType; import mage.game.mulligan.Mulligan; import mage.game.permanent.Battlefield; @@ -134,7 +133,7 @@ public interface Game extends MageItem, Serializable { default boolean isActivePlayer(UUID playerId) { - return getActivePlayerId().equals(playerId); + return getActivePlayerId() != null && getActivePlayerId().equals(playerId); } /** @@ -433,7 +432,7 @@ public interface Game extends MageItem, Serializable { // game cheats (for tests only) void cheat(UUID ownerId, Map commands); - void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard); + void cheat(UUID ownerId, UUID activePlayerId, List library, List hand, List battlefield, List graveyard, List command); // controlling the behaviour of replacement effects while permanents entering the battlefield void setScopeRelevant(boolean scopeRelevant); diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index 96de6b8306..47a6ee79a4 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -40,7 +40,6 @@ public abstract class GameCommanderImpl extends GameImpl { @Override protected void init(UUID choosingPlayerId) { - Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); //Move commander to command zone for (UUID playerId : state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); @@ -49,7 +48,7 @@ public abstract class GameCommanderImpl extends GameImpl { for (UUID commanderId : player.getCommandersIds()) { Card commander = this.getCard(commanderId); if (commander != null) { - initCommander(commander, ability, player); + initCommander(commander, player); } } } else { @@ -57,20 +56,20 @@ public abstract class GameCommanderImpl extends GameImpl { Card commander = this.getCard(player.getSideboard().iterator().next()); if (commander != null) { player.addCommanderId(commander.getId()); - initCommander(commander, ability, player); + initCommander(commander, player); } } } } } - this.getState().addAbility(ability, null); super.init(choosingPlayerId); if (startingPlayerSkipsDraw) { state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW)); } } - private void initCommander(Card commander, Ability ability, Player player) { + public void initCommander(Card commander, Player player) { + Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); commander.moveToZone(Zone.COMMAND, null, this, true); commander.getAbilities().setControllerId(player.getId()); ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary)); @@ -79,6 +78,7 @@ public abstract class GameCommanderImpl extends GameImpl { CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), checkCommanderDamage); getState().addWatcher(watcher); watcher.addCardInfoToCommander(this); + this.getState().addAbility(ability, null); } //20130711 diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 58b50ee951..94772becac 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -40,7 +40,6 @@ import mage.game.command.Emblem; import mage.game.command.Plane; import mage.game.events.*; import mage.game.events.TableEvent.EventType; -import mage.game.mulligan.LondonMulligan; import mage.game.mulligan.Mulligan; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; @@ -128,7 +127,7 @@ public abstract class GameImpl implements Game, Serializable { private int priorityTime; private final int startLife; - protected PlayerList playerList; + protected PlayerList playerList; // auto-generated from state, don't copy // infinite loop check (no copy of this attributes neccessary) private int infiniteLoopCounter; // used to check if the game is in an infinite loop @@ -138,8 +137,9 @@ public abstract class GameImpl implements Game, Serializable { // used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist) protected Map enterWithCounters = new HashMap<>(); - // used to proceed player conceding requests - private final LinkedList concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game + + // temporary store for income concede commands, don't copy + private final LinkedList concedingPlayers = new LinkedList<>(); public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { this.id = UUID.randomUUID(); @@ -2851,25 +2851,40 @@ public abstract class GameImpl implements Game, Serializable { } @Override - public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard) { + public void cheat(UUID ownerId, UUID activePlayerId, List library, List hand, List battlefield, List graveyard, List command) { Player player = getPlayer(ownerId); if (player != null) { loadCards(ownerId, library); loadCards(ownerId, hand); loadCards(ownerId, battlefield); loadCards(ownerId, graveyard); + loadCards(ownerId, command); for (Card card : library) { player.getLibrary().putOnTop(card, this); } + for (Card card : hand) { card.setZone(Zone.HAND, this); player.getHand().add(card); } + for (Card card : graveyard) { card.setZone(Zone.GRAVEYARD, this); player.getGraveyard().add(card); } + + // as commander (only commander games, look at init code in GameCommanderImpl) + if (this instanceof GameCommanderImpl) { + for (Card card : command) { + player.addCommanderId(card.getId()); + // no needs in initCommander call -- it's uses on game startup (init) + } + } else if (!command.isEmpty()) { + throw new IllegalArgumentException("Command zone supports in commander test games"); + } + + // warning, permanents go to battlefield without resolve, continuus effects must be init for (PermanentCard permanentCard : battlefield) { permanentCard.setZone(Zone.BATTLEFIELD, this); permanentCard.setOwnerId(ownerId); @@ -2882,6 +2897,14 @@ public abstract class GameImpl implements Game, Serializable { if (permanentCard.isTapped()) { newPermanent.setTapped(true); } + + // init effects on static abilities (init continuous effects, warning, game state contains copy) + for (ContinuousEffect effect : this.getState().getContinuousEffects().getLayeredEffects(this)) { + Optional ability = this.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst(); + if (ability.isPresent() && newPermanent.getId().equals(ability.get().getSourceId())) { + effect.init(ability.get(), this, activePlayerId); // game is not setup yet, game.activePlayer is null -- need direct id + } + } } applyEffects(); } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index fb7f030e74..ffd3c42ebb 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -569,17 +569,20 @@ public class GameState implements Serializable, Copyable { combat.checkForRemoveFromCombat(game); } - // Remove End of Combat effects + // remove end of combat effects public void removeEocEffects(Game game) { effects.removeEndOfCombatEffects(); delayed.removeEndOfCombatAbilities(); game.applyEffects(); } + // remove end of turn effects public void removeEotEffects(Game game) { - effects.removeEndOfTurnEffects(); - delayed.removeEndOfTurnAbilities(); + effects.removeEndOfTurnEffects(game); + delayed.removeEndOfTurnAbilities(game); + exile.cleanupEndOfTurnZones(game); game.applyEffects(); + effects.incYourTurnNumPlayed(game); } public void addEffect(ContinuousEffect effect, Ability source) { @@ -787,7 +790,7 @@ public class GameState implements Serializable, Copyable { public void addCard(Card card) { setZone(card.getId(), Zone.OUTSIDE); for (Ability ability : card.getAbilities()) { - addAbility(ability, card); + addAbility(ability, null, card); } } diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index 78c7fdb0ec..48e1efa89f 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -1,9 +1,5 @@ - package mage.game; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -21,8 +17,11 @@ import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author JRHerlehy */ public abstract class GameTinyLeadersImpl extends GameImpl { @@ -43,7 +42,6 @@ public abstract class GameTinyLeadersImpl extends GameImpl { @Override protected void init(UUID choosingPlayerId) { - Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); //Move tiny leader to command zone for (UUID playerId : state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); @@ -55,6 +53,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl { this.loadCards(cards, playerId); player.addCommanderId(commander.getId()); commander.moveToZone(Zone.COMMAND, null, this, true); + Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary)); ability.addEffect(new CommanderCostModification(commander.getId())); // Commander rule #4 was removed Jan. 18, 2016 @@ -63,13 +62,13 @@ public abstract class GameTinyLeadersImpl extends GameImpl { CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), false); getState().addWatcher(watcher); watcher.addCardInfoToCommander(this); + this.getState().addAbility(ability, null); } else { throw new UnknownError("Commander card could not be created. Name: [" + player.getMatchPlayer().getDeck().getName() + ']'); } } } - this.getState().addAbility(ability, null); super.init(choosingPlayerId); if (startingPlayerSkipsDraw) { state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW)); diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 66ab934544..0f2e85fddc 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -461,18 +461,14 @@ public class Combat implements Serializable, Copyable { creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack); // No need to attack a special defender if (defendersForcedToAttack.isEmpty()) { - if (defenders.size() == 1) { - player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false); + if (defendersCostlessAttackable.size() == 1) { + player.declareAttacker(creature.getId(), defendersCostlessAttackable.iterator().next(), game, false); } else { - if (!player.isHuman()) { // computer only for multiple defenders - player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false); - } else { // human players only for multiple defenders - TargetDefender target = new TargetDefender(defenders, creature.getId()); - target.setRequired(true); - target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack"); - if (player.chooseTarget(Outcome.Damage, target, null, game)) { - player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); - } + TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId()); + target.setRequired(true); + target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)"); + if (player.chooseTarget(Outcome.Damage, target, null, game)) { + player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); } } } else { @@ -481,6 +477,7 @@ public class Combat implements Serializable, Copyable { } else { TargetDefender target = new TargetDefender(defendersForcedToAttack, creature.getId()); target.setRequired(true); + target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)"); if (player.chooseTarget(Outcome.Damage, target, null, game)) { player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); } @@ -555,7 +552,8 @@ public class Combat implements Serializable, Copyable { } for (UUID attackingCreatureID : game.getCombat().getAttackers()) { Permanent permanent = game.getPermanent(attackingCreatureID); - if (permanent != null && permanent.getBlocking() == 0) { + CombatGroup group = game.getCombat().findGroup(attackingCreatureID); + if (permanent != null && group != null && !group.getBlocked()) { game.fireEvent(GameEvent.getEvent(EventType.UNBLOCKED_ATTACKER, attackingCreatureID, attackingPlayerId)); } } @@ -1539,6 +1537,18 @@ public class Combat implements Serializable, Copyable { return false; } + public boolean isPlaneswalkerAttacked(UUID defenderId, Game game) { + for (CombatGroup group : groups) { + if (group.defenderIsPlaneswalker) { + Permanent permanent = game.getPermanent(group.getDefenderId()); + if (permanent.isControlledBy(defenderId)) { + return true; + } + } + } + return false; + } + /** * @param attackerId * @return uuid of defending player or planeswalker diff --git a/Mage/src/main/java/mage/game/command/emblems/ElspethKnightErrantEmblem.java b/Mage/src/main/java/mage/game/command/emblems/ElspethKnightErrantEmblem.java index 1789969181..b6b292b238 100644 --- a/Mage/src/main/java/mage/game/command/emblems/ElspethKnightErrantEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/ElspethKnightErrantEmblem.java @@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.command.Emblem; /** - * * @author spjspj */ public final class ElspethKnightErrantEmblem extends Emblem { @@ -28,7 +27,7 @@ public final class ElspethKnightErrantEmblem extends Emblem { new CardTypePredicate(CardType.ENCHANTMENT), new CardTypePredicate(CardType.LAND))); Effect effect = new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter, false); - effect.setText("Artifacts, creatures, enchantments, and lands you control are indestructible"); + effect.setText("Artifacts, creatures, enchantments, and lands you control have indestructible"); this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, effect)); this.setExpansionSetCodeForImage("MMA"); } diff --git a/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java b/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java index 1984d36f53..0de0f51a96 100644 --- a/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java @@ -64,10 +64,12 @@ class JayaBallardCastFromGraveyardEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { Card card = game.getCard(objectId); - if (card != null) { - return (affectedControllerId.equals(source.getControllerId()) - && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game) - && Zone.GRAVEYARD.equals(game.getState().getZone(card.getId()))); + if (card != null + && affectedControllerId.equals(source.getControllerId()) + && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(card, game) + && Zone.GRAVEYARD.equals(game.getState().getZone(card.getId()))) { + game.getState().setValue("JayaBallard", card); + return true; } return false; } @@ -98,7 +100,7 @@ class JayaBallardReplacementEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); + Card card = (Card) game.getState().getValue("JayaBallard"); if (card != null) { controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.STACK, true); return true; @@ -116,11 +118,13 @@ class JayaBallardReplacementEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { if (Zone.GRAVEYARD == ((ZoneChangeEvent) event).getToZone()) { Card card = game.getCard(event.getSourceId()); - if (card != null && (card.isInstant() || card.isSorcery())) { - // TODO: Find a way to check, that the spell from graveyard was really cast by the ability of the emblem. - // currently every spell cast from graveyard will be exiled. + if (card != null + && (card.isInstant() + || card.isSorcery())) { CastFromGraveyardWatcher watcher = game.getState().getWatcher(CastFromGraveyardWatcher.class); - return watcher != null && watcher.spellWasCastFromGraveyard(event.getTargetId(), game.getState().getZoneChangeCounter(event.getTargetId())); + return watcher != null + && watcher.spellWasCastFromGraveyard(event.getTargetId(), + game.getState().getZoneChangeCounter(event.getTargetId())); } } return false; diff --git a/Mage/src/main/java/mage/game/command/emblems/NissaWhoShakesTheWorldEmblem.java b/Mage/src/main/java/mage/game/command/emblems/NissaWhoShakesTheWorldEmblem.java new file mode 100644 index 0000000000..e00b2a2480 --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/NissaWhoShakesTheWorldEmblem.java @@ -0,0 +1,28 @@ + +package mage.game.command.emblems; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.command.Emblem; + +/** + * @author TheElk801 + */ +public final class NissaWhoShakesTheWorldEmblem extends Emblem { + + public NissaWhoShakesTheWorldEmblem() { + this.setName("Emblem Nissa"); + this.getAbilities().add(new SimpleStaticAbility( + Zone.COMMAND, + new GainAbilityAllEffect( + IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, false + ) + )); + this.setExpansionSetCodeForImage("WAR"); + } +} diff --git a/Mage/src/main/java/mage/game/draft/RichManBoosterDraft.java b/Mage/src/main/java/mage/game/draft/RichManBoosterDraft.java index b2aecbd1d8..da3fd7d8da 100644 --- a/Mage/src/main/java/mage/game/draft/RichManBoosterDraft.java +++ b/Mage/src/main/java/mage/game/draft/RichManBoosterDraft.java @@ -6,6 +6,7 @@ import java.util.Objects; import java.util.UUID; import mage.cards.Card; import mage.cards.ExpansionSet; +import org.apache.log4j.Logger; /** * @@ -13,7 +14,10 @@ import mage.cards.ExpansionSet; */ public class RichManBoosterDraft extends DraftImpl { - protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25}; + private static final Logger logger = Logger.getLogger(RichManBoosterDraft.class); + + //protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25}; + protected int[] richManTimes = {70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40}; public RichManBoosterDraft(DraftOptions options, List sets) { super(options, sets); diff --git a/Mage/src/main/java/mage/game/draft/RichManCubeBoosterDraft.java b/Mage/src/main/java/mage/game/draft/RichManCubeBoosterDraft.java index 1c097da3b9..082d82deb5 100644 --- a/Mage/src/main/java/mage/game/draft/RichManCubeBoosterDraft.java +++ b/Mage/src/main/java/mage/game/draft/RichManCubeBoosterDraft.java @@ -13,7 +13,8 @@ import mage.game.draft.DraftCube.CardIdentity; */ public class RichManCubeBoosterDraft extends DraftImpl { - protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25}; + //protected int[] richManTimes = {75, 70, 65, 60, 55, 50, 45, 40, 35, 35, 35, 35, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25}; + protected int[] richManTimes = {70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40}; protected final Map cardsInCube = new LinkedHashMap<>(); public RichManCubeBoosterDraft(DraftOptions options, List sets) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index edff7314ab..6d1705abe1 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -18,6 +18,9 @@ public class GameEvent implements Serializable { protected UUID sourceId; protected UUID playerId; protected int amount; + // flags: + // for counters: event is result of effect (+1 from planeswalkers is cost, not effect) + // for combat damage: event is preventable damage protected boolean flag; protected String data; protected Zone zone; @@ -292,7 +295,7 @@ public class GameEvent implements Serializable { UNATTACH, UNATTACHED, ADD_COUNTER, COUNTER_ADDED, ADD_COUNTERS, COUNTERS_ADDED, - COUNTER_REMOVED, + COUNTER_REMOVED, COUNTERS_REMOVED, LOSE_CONTROL, /* LOST_CONTROL targetId id of the creature that lost control @@ -433,6 +436,17 @@ public class GameEvent implements Serializable { this.amount = amount; } + public void setAmountForCounters(int amount, boolean isEffect) { + this.amount = amount; + + // cost event must be "transformed" to effect event, as example: + // planeswalker's +1 cost will be affected by Pir, Imaginative Rascal (1 + 1) and applied as effect by Doubling Season (2 * 2) + // https://github.com/magefree/mage/issues/5802 + if (isEffect) { + setFlag(true); + } + } + public boolean getFlag() { return flag; } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index a76df41f43..725e077df0 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -909,7 +909,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (countersToRemove > getCounters(game).getCount(CounterType.LOYALTY)) { countersToRemove = getCounters(game).getCount(CounterType.LOYALTY); } - getCounters(game).removeCounter(CounterType.LOYALTY, countersToRemove); + removeCounters(CounterType.LOYALTY.getName(), countersToRemove, game); game.fireEvent(new DamagedPlaneswalkerEvent(objectId, sourceId, controllerId, actualDamage, combat)); return actualDamage; } diff --git a/Mage/src/main/java/mage/game/permanent/token/AssassinToken2.java b/Mage/src/main/java/mage/game/permanent/token/AssassinToken2.java index 079e756d71..5f7b407718 100644 --- a/Mage/src/main/java/mage/game/permanent/token/AssassinToken2.java +++ b/Mage/src/main/java/mage/game/permanent/token/AssassinToken2.java @@ -26,6 +26,8 @@ public final class AssassinToken2 extends TokenImpl { toughness = new MageInt(1); addAbility(DeathtouchAbility.getInstance()); addAbility(new AssassinToken2TriggeredAbility()); + + setOriginalExpansionSetCode("WAR"); } private AssassinToken2(final AssassinToken2 token) { @@ -40,7 +42,7 @@ public final class AssassinToken2 extends TokenImpl { class AssassinToken2TriggeredAbility extends TriggeredAbilityImpl { AssassinToken2TriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect()); + super(Zone.BATTLEFIELD, null); } private AssassinToken2TriggeredAbility(final AssassinToken2TriggeredAbility effect) { @@ -60,9 +62,10 @@ class AssassinToken2TriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getSourceId().equals(getSourceId())) { - for (Effect effect : this.getAllEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + Effect effect = new DestroyTargetEffect(); + effect.setTargetPointer(new FixedTarget(event.getTargetId(), game)); + this.getEffects().clear(); + this.addEffect(effect); return true; } return false; diff --git a/Mage/src/main/java/mage/game/permanent/token/DragonToken.java b/Mage/src/main/java/mage/game/permanent/token/DragonToken.java index 0d441de1ca..babf74f8df 100644 --- a/Mage/src/main/java/mage/game/permanent/token/DragonToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/DragonToken.java @@ -1,17 +1,15 @@ - - package mage.game.permanent.token; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import mage.MageInt; import mage.abilities.keyword.FlyingAbility; import mage.constants.CardType; import mage.constants.SubType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** - * * @author BetaSteward_at_googlemail.com */ public final class DragonToken extends TokenImpl { @@ -19,7 +17,7 @@ public final class DragonToken extends TokenImpl { static final private List tokenImageSets = new ArrayList<>(); static { - tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17")); + tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17", "WAR")); } public DragonToken() { diff --git a/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java b/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java index 70b12ee68e..e78dee90ab 100644 --- a/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java @@ -19,7 +19,7 @@ public final class GoblinToken extends TokenImpl { static { tokenImageSets.addAll(Arrays.asList("10E", "ALA", "SOM", "M10", "NPH", "M13", "RTR", "MMA", "M15", "C14", "KTK", "EVG", "DTK", "ORI", "DDG", "DDN", "DD3EVG", "MM2", - "MM3", "EMA", "C16", "DOM", "ANA", "RNA")); + "MM3", "EMA", "C16", "DOM", "ANA", "RNA", "WAR")); } public GoblinToken(boolean withHaste) { diff --git a/Mage/src/main/java/mage/game/permanent/token/GodEternalOketraToken.java b/Mage/src/main/java/mage/game/permanent/token/GodEternalOketraToken.java new file mode 100644 index 0000000000..37e965bdbb --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/GodEternalOketraToken.java @@ -0,0 +1,33 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class GodEternalOketraToken extends TokenImpl { + + public GodEternalOketraToken() { + super("Zombie Warrior", "4/4 black Zombie Warrior creature token with vigilance"); + setOriginalExpansionSetCode("WAR"); // default + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.ZOMBIE); + subtype.add(SubType.WARRIOR); + power = new MageInt(4); + toughness = new MageInt(4); + addAbility(VigilanceAbility.getInstance()); + } + + private GodEternalOketraToken(final GodEternalOketraToken token) { + super(token); + } + + @Override + public GodEternalOketraToken copy() { + return new GodEternalOketraToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/PlanewideCelebrationToken.java b/Mage/src/main/java/mage/game/permanent/token/PlanewideCelebrationToken.java new file mode 100644 index 0000000000..5405ab5235 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/PlanewideCelebrationToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * + * @author TheElk801 + */ +public final class PlanewideCelebrationToken extends TokenImpl { + + public PlanewideCelebrationToken() { + super("Citizen", "2/2 Citizen creature token that's all colors"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + color.setBlue(true); + color.setBlack(true); + color.setRed(true); + color.setGreen(true); + + subtype.add(SubType.CITIZEN); + power = new MageInt(2); + toughness = new MageInt(2); + } + + public PlanewideCelebrationToken(final PlanewideCelebrationToken token) { + super(token); + } + + public PlanewideCelebrationToken copy() { + return new PlanewideCelebrationToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/RevelOfTheFallenGodSatyrToken.java b/Mage/src/main/java/mage/game/permanent/token/RevelOfTheFallenGodSatyrToken.java deleted file mode 100644 index 231f7f3a14..0000000000 --- a/Mage/src/main/java/mage/game/permanent/token/RevelOfTheFallenGodSatyrToken.java +++ /dev/null @@ -1,35 +0,0 @@ - -package mage.game.permanent.token; - -import mage.constants.CardType; -import mage.constants.SubType; -import mage.MageInt; -import mage.ObjectColor; -import mage.abilities.keyword.HasteAbility; - -/** - * - * @author spjspj - */ -public final class RevelOfTheFallenGodSatyrToken extends TokenImpl { - - public RevelOfTheFallenGodSatyrToken() { - super("Satyr", "2/2 red and green Satyr creature tokens with haste"); - this.setOriginalExpansionSetCode("THS"); - cardType.add(CardType.CREATURE); - color.setColor(ObjectColor.RED); - color.setColor(ObjectColor.GREEN); - subtype.add(SubType.SATYR); - power = new MageInt(2); - toughness = new MageInt(2); - addAbility(HasteAbility.getInstance()); - } - - public RevelOfTheFallenGodSatyrToken(final RevelOfTheFallenGodSatyrToken token) { - super(token); - } - - public RevelOfTheFallenGodSatyrToken copy() { - return new RevelOfTheFallenGodSatyrToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/ServoToken.java b/Mage/src/main/java/mage/game/permanent/token/ServoToken.java index 4afbc898ec..43fc61957d 100644 --- a/Mage/src/main/java/mage/game/permanent/token/ServoToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/ServoToken.java @@ -19,6 +19,7 @@ public final class ServoToken extends TokenImpl { static { tokenImageSets.addAll(Collections.singletonList("KLD")); + tokenImageSets.addAll(Collections.singletonList("WAR")); } public ServoToken() { diff --git a/Mage/src/main/java/mage/game/permanent/token/SoldierVigilanceToken.java b/Mage/src/main/java/mage/game/permanent/token/SoldierVigilanceToken.java new file mode 100644 index 0000000000..34a79a9df8 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SoldierVigilanceToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SoldierVigilanceToken extends TokenImpl { + + public SoldierVigilanceToken() { + super("Soldier", "2/2 white Soldier creature token with vigilance"); + + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.SOLDIER); + power = new MageInt(2); + toughness = new MageInt(2); + addAbility(VigilanceAbility.getInstance()); + + setOriginalExpansionSetCode("WAR"); + } + + private SoldierVigilanceToken(final SoldierVigilanceToken token) { + super(token); + } + + @Override + public SoldierVigilanceToken copy() { + return new SoldierVigilanceToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/TeyoToken.java b/Mage/src/main/java/mage/game/permanent/token/TeyoToken.java index 229192175c..251ec6ca7d 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TeyoToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/TeyoToken.java @@ -15,6 +15,8 @@ public final class TeyoToken extends TokenImpl { power = new MageInt(0); toughness = new MageInt(3); addAbility(DefenderAbility.getInstance()); + + setOriginalExpansionSetCode("WAR"); } public TeyoToken(final TeyoToken token) { diff --git a/Mage/src/main/java/mage/game/permanent/token/UginTheIneffableToken.java b/Mage/src/main/java/mage/game/permanent/token/UginTheIneffableToken.java new file mode 100644 index 0000000000..8bbb0002e2 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/UginTheIneffableToken.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class UginTheIneffableToken extends TokenImpl { + + public UginTheIneffableToken() { + super("Spirit", "2/2 colorless Spirit creature token"); + setExpansionSetCodeForImage("WAR"); // default + cardType.add(CardType.CREATURE); + subtype.add(SubType.SPIRIT); + power = new MageInt(2); + toughness = new MageInt(2); + } + + private UginTheIneffableToken(final UginTheIneffableToken token) { + super(token); + } + + @Override + public UginTheIneffableToken copy() { + return new UginTheIneffableToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/VojaFriendToElvesToken.java b/Mage/src/main/java/mage/game/permanent/token/VojaFriendToElvesToken.java new file mode 100644 index 0000000000..cb8c66b133 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/VojaFriendToElvesToken.java @@ -0,0 +1,35 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +/** + * @author TheElk801 + */ +public final class VojaFriendToElvesToken extends TokenImpl { + + public VojaFriendToElvesToken() { + super("Voja, Friend to Elves", "Voja, Friend to Elves, a legendary 3/3 green and white Wolf creature token"); + this.cardType.add(CardType.CREATURE); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.WOLF); + + this.color.setGreen(true); + this.color.setWhite(true); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + setOriginalExpansionSetCode("WAR"); + } + + private VojaFriendToElvesToken(final VojaFriendToElvesToken token) { + super(token); + } + + public VojaFriendToElvesToken copy() { + return new VojaFriendToElvesToken(this); + } + +} diff --git a/Mage/src/main/java/mage/game/permanent/token/WizardToken.java b/Mage/src/main/java/mage/game/permanent/token/WizardToken.java new file mode 100644 index 0000000000..ecc864261f --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/WizardToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +public final class WizardToken extends TokenImpl { + + public WizardToken() { + this("WAR"); + } + + public WizardToken(String setCode) { + super("Wizard", "2/2 blue Wizard creature token"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.WIZARD); + color.setBlue(true); + power = new MageInt(2); + toughness = new MageInt(2); + + setOriginalExpansionSetCode(setCode); + } + + private WizardToken(final WizardToken token) { + super(token); + } + + public WizardToken copy() { + return new WizardToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/WolfToken.java b/Mage/src/main/java/mage/game/permanent/token/WolfToken.java index 1172fb2a32..8f1a88da77 100644 --- a/Mage/src/main/java/mage/game/permanent/token/WolfToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/WolfToken.java @@ -18,7 +18,7 @@ public final class WolfToken extends TokenImpl { static final private List tokenImageSets = new ArrayList<>(); static { - tokenImageSets.addAll(Arrays.asList("BNG", "C14", "CNS", "FNMP", "ISD", "LRW", "M10", "M14", "MM2", "SHM", "SOM", "ZEN", "SOI", "C15", "M15")); + tokenImageSets.addAll(Arrays.asList("BNG", "C14", "CNS", "FNMP", "ISD", "LRW", "M10", "M14", "MM2", "SHM", "SOM", "ZEN", "SOI", "C15", "M15", "WAR")); } public WolfToken() { diff --git a/Mage/src/main/java/mage/game/permanent/token/ZombieToken.java b/Mage/src/main/java/mage/game/permanent/token/ZombieToken.java index dc5d7fad46..397005e22f 100644 --- a/Mage/src/main/java/mage/game/permanent/token/ZombieToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/ZombieToken.java @@ -18,7 +18,7 @@ public final class ZombieToken extends TokenImpl { static { tokenImageSets.addAll(Arrays.asList("10E", "M10", "M11", "M12", "M13", "M14", "M15", "MBS", "ALA", "ISD", "C14", "C15", "C16", "C17", "CNS", - "MMA", "BNG", "KTK", "DTK", "ORI", "OGW", "SOI", "EMN", "EMA", "MM3", "AKH", "CMA", "E01", "RNA")); + "MMA", "BNG", "KTK", "DTK", "ORI", "OGW", "SOI", "EMN", "EMA", "MM3", "AKH", "CMA", "E01", "RNA", "WAR")); } public ZombieToken() { diff --git a/Mage/src/main/java/mage/game/stack/StackObjImpl.java b/Mage/src/main/java/mage/game/stack/StackObjImpl.java index beadf908f5..8af461dfdb 100644 --- a/Mage/src/main/java/mage/game/stack/StackObjImpl.java +++ b/Mage/src/main/java/mage/game/stack/StackObjImpl.java @@ -188,6 +188,11 @@ public abstract class StackObjImpl implements StackObject { newTarget.clearChosen(); } } + + // workaround to stop infinite AI choose (remove after chooseTarget can be called with extra filter to disable some ids) + if (iteration > 10) { + break; + } } while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); // choose a new target diff --git a/Mage/src/main/java/mage/game/tournament/LimitedOptions.java b/Mage/src/main/java/mage/game/tournament/LimitedOptions.java index c8199d9200..0151beed80 100644 --- a/Mage/src/main/java/mage/game/tournament/LimitedOptions.java +++ b/Mage/src/main/java/mage/game/tournament/LimitedOptions.java @@ -1,15 +1,13 @@ - - package mage.game.tournament; +import mage.cards.decks.Deck; +import mage.game.draft.DraftCube; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import mage.cards.decks.Deck; -import mage.game.draft.DraftCube; /** - * * @author BetaSteward_at_googlemail.com */ public class LimitedOptions implements Serializable { @@ -20,6 +18,7 @@ public class LimitedOptions implements Serializable { protected DraftCube draftCube; protected int numberBoosters; protected boolean isRandom; + protected boolean isRichMan; protected Deck cubeFromDeck = null; public List getSetCodes() { @@ -66,11 +65,19 @@ public class LimitedOptions implements Serializable { this.numberBoosters = numberBoosters; } - public boolean getIsRandom(){ + public boolean getIsRandom() { return isRandom; } - public void setIsRandom(boolean isRandom){ + + public void setIsRandom(boolean isRandom) { this.isRandom = isRandom; } + public boolean getIsRichMan() { + return isRichMan; + } + + public void setIsRichMan(boolean isRichMan) { + this.isRichMan = isRichMan; + } } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index f40c204b6e..359f254256 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -1,11 +1,5 @@ - package mage.game.turn; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.constants.PhaseStep; import mage.constants.TurnPhase; @@ -18,8 +12,13 @@ import mage.game.stack.StackObject; import mage.players.Player; import mage.util.ThreadLocalStringBuilder; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class Turn implements Serializable { @@ -93,7 +92,6 @@ public class Turn implements Serializable { } /** - * * @param game * @param activePlayer * @return true if turn is skipped @@ -105,6 +103,7 @@ public class Turn implements Serializable { return false; } + if (game.getState().getTurnMods().skipTurn(activePlayer.getId())) { game.informPlayers(activePlayer.getLogName() + " skips their turn."); return true; @@ -239,6 +238,7 @@ public class Turn implements Serializable { this.play(game, activePlayerId); } }*/ + /** * Used for some spells with end turn effect (e.g. Time Stop). * diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 286b98f5ac..6dc09c8f9d 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -337,20 +337,21 @@ public interface Player extends MageItem, Copyable { boolean removeFromLibrary(Card card, Game game); - boolean searchLibrary(TargetCardInLibrary target, Game game); + boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game); - boolean searchLibrary(TargetCardInLibrary target, Game game, boolean triggerEvents); + boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, boolean triggerEvents); - boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId); + boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId); /** * @param target + * @param source * @param game * @param targetPlayerId player whose library will be searched * @param triggerEvents whether searching will trigger any game events * @return true if search was successful */ - boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents); + boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); /** * Reveals all players' libraries. Useful for abilities like Jace, Architect of Thought's -8 @@ -568,6 +569,7 @@ public interface Player extends MageItem, Copyable { // set the value for non mana X costs int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost); + // TODO: rework choose replacement effects to use array, not map (it'a random order now) int chooseReplacementEffect(Map abilityMap, Game game); TriggeredAbility chooseTriggeredAbility(List abilities, Game game); @@ -653,9 +655,10 @@ public interface Player extends MageItem, Copyable { * * @param card * @param game + * @param abilitiesToActivate extra info about abilities that can be activated on NO option * @return player looked at the card */ - boolean lookAtFaceDownCard(Card card, Game game); + boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); /** * Set seconds left to play the game. diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index b36a18d108..9df46c83ea 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -70,7 +70,6 @@ import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; -import java.util.stream.Collectors; public abstract class PlayerImpl implements Player, Serializable { @@ -183,8 +182,6 @@ public abstract class PlayerImpl implements Player, Serializable { protected final Map silentPhaseSteps = ImmutableMap.builder(). put(PhaseStep.DECLARE_ATTACKERS, Step.StepPart.PRE).build(); - - public PlayerImpl(String name, RangeOfInfluence range) { this(UUID.randomUUID()); this.name = name; @@ -613,7 +610,7 @@ public abstract class PlayerImpl implements Player, Serializable { } if (abilities.containsKey(HexproofAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) - && null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, this.getId(), game)) { + && null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { return false; } } @@ -739,9 +736,9 @@ public abstract class PlayerImpl implements Player, Serializable { TargetDiscard target = new TargetDiscard(possibleAmount, possibleAmount, new FilterCard(CardUtil.numberToText(possibleAmount, "a") + " card" + (possibleAmount > 1 ? "s" : "")), playerId); choose(Outcome.Discard, target, source == null ? null : source.getSourceId(), game); for (UUID cardId : target.getTargets()) { - if(discard(this.getHand().get(cardId, game), source, game)) { - discardedCards.add(cardId); - } + if (discard(this.getHand().get(cardId, game), source, game)) { + discardedCards.add(cardId); + } } } return discardedCards; @@ -882,30 +879,27 @@ public abstract class PlayerImpl implements Player, Serializable { if (!cardsToLibrary.isEmpty()) { Cards cards = new CardsImpl(cardsToLibrary); // prevent possible ConcurrentModificationException if (!anyOrder) { - while (!cards.isEmpty()) { - Card card = cards.getRandom(game); - if (card != null) { - cards.remove(card); - moveObjectToLibrary(card.getId(), source == null ? null : source.getSourceId(), game, false, false); - } else { - return false;// probably cards were removed because player left the game - } + // random order + List ids = new ArrayList<>(cards); + Collections.shuffle(ids); + for (UUID id : ids) { + moveObjectToLibrary(id, source == null ? null : source.getSourceId(), game, false, false); } } else { + // user defined order TargetCard target = new TargetCard(Zone.ALL, new FilterCard("card ORDER to put on the BOTTOM of your library (last one chosen will be bottommost)")); target.setRequired(true); - while (cards.size() > 1) { - this.choose(Outcome.Neutral, cards, target, game); - if (!canRespond()) { - return false; - } + while (cards.size() > 1 && this.canRespond() && this.choose(Outcome.Neutral, cards, target, game)) { UUID targetObjectId = target.getFirstTarget(); + if (targetObjectId == null) { + break; + } cards.remove(targetObjectId); moveObjectToLibrary(targetObjectId, source == null ? null : source.getSourceId(), game, false, false); target.clearChosen(); } - if (cards.size() == 1) { - moveObjectToLibrary(cards.iterator().next(), source == null ? null : source.getSourceId(), game, false, false); + for (UUID c : cards) { + moveObjectToLibrary(c, source == null ? null : source.getSourceId(), game, false, false); } } } @@ -947,30 +941,27 @@ public abstract class PlayerImpl implements Player, Serializable { Cards cards = new CardsImpl(cardsToLibrary); // prevent possible ConcurrentModificationException UUID sourceId = (source == null ? null : source.getSourceId()); if (!anyOrder) { - while (!cards.isEmpty()) { - Card card = cards.getRandom(game); - if (card != null) { - cards.remove(card.getId()); - moveObjectToLibrary(card.getId(), source == null ? null : source.getSourceId(), game, true, false); - } else { - return false; // probably cards were removed because player left the game - } + // random order + List ids = new ArrayList<>(cards); + Collections.shuffle(ids); + for (UUID id : ids) { + moveObjectToLibrary(id, source == null ? null : source.getSourceId(), game, true, false); } } else { - TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card ORDER to put on the TOP of your library (last one chosen will be topmost)")); + // user defined order + TargetCard target = new TargetCard(Zone.ALL, new FilterCard("card ORDER to put on the TOP of your library (last one chosen will be topmost)")); target.setRequired(true); - while (cards.size() > 1) { - this.choose(Outcome.Neutral, cards, target, game); - if (!canRespond()) { - return false; - } + while (cards.size() > 1 && this.canRespond() && this.choose(Outcome.Neutral, cards, target, game)) { UUID targetObjectId = target.getFirstTarget(); + if (targetObjectId == null) { + break; + } cards.remove(targetObjectId); - moveObjectToLibrary(targetObjectId, sourceId, game, true, false); + moveObjectToLibrary(targetObjectId, source == null ? null : source.getSourceId(), game, true, false); target.clearChosen(); } - if (cards.size() == 1) { - moveObjectToLibrary(cards.iterator().next(), sourceId, game, true, false); + for (UUID c : cards) { + moveObjectToLibrary(c, source == null ? null : source.getSourceId(), game, true, false); } } } @@ -2019,24 +2010,30 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean addCounters(Counter counter, Game game) { boolean returnCode = true; - GameEvent countersEvent = GameEvent.getEvent(EventType.ADD_COUNTERS, playerId, null, playerId, counter.getName(), counter.getCount()); - if (!game.replaceEvent(countersEvent)) { - int amount = countersEvent.getAmount(); + GameEvent addingAllEvent = GameEvent.getEvent(EventType.ADD_COUNTERS, playerId, null, playerId, counter.getName(), counter.getCount()); + if (!game.replaceEvent(addingAllEvent)) { + int amount = addingAllEvent.getAmount(); int finalAmount = amount; + boolean isEffectFlag = addingAllEvent.getFlag(); for (int i = 0; i < amount; i++) { Counter eventCounter = counter.copy(); eventCounter.remove(eventCounter.getCount() - 1); - GameEvent event = GameEvent.getEvent(EventType.ADD_COUNTER, playerId, null, playerId, counter.getName(), 1); - if (!game.replaceEvent(event)) { + GameEvent addingOneEvent = GameEvent.getEvent(EventType.ADD_COUNTER, playerId, null, playerId, counter.getName(), 1); + addingOneEvent.setFlag(isEffectFlag); + if (!game.replaceEvent(addingOneEvent)) { getCounters().addCounter(eventCounter); - game.fireEvent(GameEvent.getEvent(EventType.COUNTER_ADDED, playerId, null, playerId, counter.getName(), 1)); + GameEvent addedOneEvent = GameEvent.getEvent(EventType.COUNTER_ADDED, playerId, null, playerId, counter.getName(), 1); + addedOneEvent.setFlag(addingOneEvent.getFlag()); + game.fireEvent(addedOneEvent); } else { finalAmount--; returnCode = false; } } if (finalAmount > 0) { - game.fireEvent(GameEvent.getEvent(EventType.COUNTERS_ADDED, playerId, null, playerId, counter.getName(), amount)); + GameEvent addedAllEvent = GameEvent.getEvent(EventType.COUNTERS_ADDED, playerId, null, playerId, counter.getName(), amount); + addedAllEvent.setFlag(addingAllEvent.getFlag()); + game.fireEvent(addedAllEvent); } } else { returnCode = false; @@ -2046,6 +2043,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void removeCounters(String name, int amount, Ability source, Game game) { + int finalAmount = 0; for (int i = 0; i < amount; i++) { if (!counters.removeCounter(name, 1)) { break; @@ -2055,7 +2053,13 @@ public abstract class PlayerImpl implements Player, Serializable { event.setData(name); event.setAmount(1); game.fireEvent(event); + finalAmount++; } + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, + getId(), (source == null ? null : source.getSourceId()), (source == null ? null : source.getControllerId())); + event.setData(name); + event.setAmount(finalAmount); + game.fireEvent(event); } protected boolean canDamage(MageObject source, Game game) { @@ -2425,22 +2429,22 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game) { - return searchLibrary(target, game, playerId, true); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game) { + return searchLibrary(target, source, game, playerId, true); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, boolean triggerEvents) { - return searchLibrary(target, game, playerId, triggerEvents); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, boolean triggerEvents) { + return searchLibrary(target, source, game, playerId, triggerEvents); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId) { - return searchLibrary(target, game, targetPlayerId, true); + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId) { + return searchLibrary(target, source, game, targetPlayerId, true); } @Override - public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents) { + public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents) { //20091005 - 701.14c Library searchedLibrary = null; String searchInfo = null; @@ -2457,7 +2461,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (searchedLibrary == null) { return false; } - GameEvent event = GameEvent.getEvent(GameEvent.EventType.SEARCH_LIBRARY, targetPlayerId, playerId, playerId, Integer.MAX_VALUE); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.SEARCH_LIBRARY, targetPlayerId, source.getSourceId(), playerId, Integer.MAX_VALUE); if (!game.replaceEvent(event)) { if (!game.isSimulation()) { game.informPlayers(searchInfo); @@ -3459,10 +3463,13 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean lookAtFaceDownCard(Card card, Game game - ) { + public boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate) { if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.LOOK_AT_FACE_DOWN, card.getSpellAbility(), this.getId(), game)) { - if (chooseUse(Outcome.Benefit, "Look at that card?", null, game)) { + // two modes: look at card or not to look and activate other abilities + String lookMessage = abilitiesToActivate > 0 ? "Look at that card (it's have " + abilitiesToActivate + " abilities to activate)?" : "Look at that card?"; + String lookYes = "Yes, look at card"; + String lookNo = abilitiesToActivate > 0 ? "No, activate ability" : "No"; + if (chooseUse(Outcome.Benefit, lookMessage, "", lookYes, lookNo, null, game)) { Cards cards = new CardsImpl(card); this.lookAtCards(getName() + " - " + sdf.format(System.currentTimeMillis()), cards, game); return true; diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java new file mode 100644 index 0000000000..04665fbbc2 --- /dev/null +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -0,0 +1,221 @@ +package mage.players; + +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.Modes; +import mage.abilities.TriggeredAbility; +import mage.abilities.costs.VariableCost; +import mage.abilities.costs.mana.ManaCost; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.decks.Deck; +import mage.choices.Choice; +import mage.constants.Outcome; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.draft.Draft; +import mage.game.match.Match; +import mage.game.permanent.Permanent; +import mage.game.tournament.Tournament; +import mage.target.Target; +import mage.target.TargetAmount; +import mage.target.TargetCard; +import mage.target.TargetPlayer; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.util.stream.Collectors.toList; + +public class StubPlayer extends PlayerImpl implements Player { + + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { + if (target instanceof TargetPlayer) { + for (Player player : game.getPlayers().values()) { + if (player.getId().equals(getId()) && target.canTarget(getId(), game)) { + target.add(player.getId(), game); + return true; + } + } + } + return false; + } + + public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game) { + cards.getCards(game).stream().map(MageItem::getId).forEach(cardId -> target.add(cardId, game)); + return true; + } + + @Override + public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + if ("cards to PUT on the BOTTOM of your library (Discard for Mulligan)".equals(target.getFilter().getMessage())) { + chooseDiscardBottom(game, target.getMinNumberOfTargets(), cards.getCards(game) + .stream().map(MageItem::getId).collect(toList())).forEach(cardId -> target.add(cardId, game)); + } else { + UUID cardId = getOnlyElement(cards.getCards(game)).getId(); + if (chooseScry(game, cardId)) { + target.add(cardId, game); + return true; + } + } + return false; + } + + public List chooseDiscardBottom(Game game, int count, List cardIds) { + return cardIds.subList(0, count); + } + + public boolean chooseScry(Game game, UUID cardId) { + return false; + } + + @Override + public void shuffleLibrary(Ability source, Game game) { + + } + + public StubPlayer(String name, RangeOfInfluence range) { + super(name, range); + } + + @Override + public void abort() { + + } + + @Override + public void skip() { + + } + + @Override + public Player copy() { + return null; + } + + @Override + public boolean priority(Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { + return false; + } + + @Override + public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseMulligan(Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + return false; + } + + @Override + public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { + return false; + } + + @Override + public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { + return false; + } + + @Override + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + return 0; + } + + @Override + public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { + return 0; + } + + @Override + public int chooseReplacementEffect(Map abilityMap, Game game) { + return 0; + } + + @Override + public TriggeredAbility chooseTriggeredAbility(List abilities, Game game) { + return null; + } + + @Override + public Mode chooseMode(Modes modes, Ability source, Game game) { + return null; + } + + @Override + public void selectAttackers(Game game, UUID attackingPlayerId) { + + } + + @Override + public void selectBlockers(Game game, UUID defendingPlayerId) { + + } + + @Override + public UUID chooseAttackerOrder(List attacker, Game game) { + return null; + } + + @Override + public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, List blockerOrder, Game game) { + return null; + } + + @Override + public void assignDamage(int damage, List targets, String singleTargetName, UUID sourceId, Game game) { + + } + + @Override + public int getAmount(int min, int max, String message, Game game) { + return 0; + } + + @Override + public void sideboard(Match match, Deck deck) { + + } + + @Override + public void construct(Tournament tournament, Deck deck) { + + } + + @Override + public void pickCard(List cards, Deck deck, Draft draft) { + + } + +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index cb6f2323d5..8d7a0e061d 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -293,22 +293,17 @@ public abstract class TargetImpl implements Target { return false; } + List possibleTargets = new ArrayList<>(possibleTargets(source.getSourceId(), playerId, game)); while (!isChosen() && !doneChosing()) { if (!player.canRespond()) { return chosen = targets.size() >= getNumberOfTargets(); } chosen = targets.size() >= getNumberOfTargets(); if (isRandom()) { - Set possibleTargets = possibleTargets(source.getSourceId(), playerId, game); if (!possibleTargets.isEmpty()) { - int i = 0; - int rnd = RandomUtil.nextInt(possibleTargets.size()); - Iterator it = possibleTargets.iterator(); - while (i < rnd) { - it.next(); - i++; - } - this.addTarget(((UUID) it.next()), source, game); + int index = RandomUtil.nextInt(possibleTargets.size()); + this.addTarget(possibleTargets.get(index), source, game); + possibleTargets.remove(index); } else { return chosen; } diff --git a/Mage/src/main/java/mage/target/TargetStackObject.java b/Mage/src/main/java/mage/target/TargetStackObject.java index a9eb6a3b4b..eeccdc2df7 100644 --- a/Mage/src/main/java/mage/target/TargetStackObject.java +++ b/Mage/src/main/java/mage/target/TargetStackObject.java @@ -1,10 +1,9 @@ - - package mage.target; -import mage.constants.Zone; import mage.abilities.Ability; +import mage.constants.Zone; import mage.filter.FilterStackObject; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.stack.StackObject; @@ -13,7 +12,6 @@ import java.util.Set; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public class TargetStackObject extends TargetObject { @@ -21,7 +19,7 @@ public class TargetStackObject extends TargetObject { protected final FilterStackObject filter; public TargetStackObject() { - this(1, 1, new FilterStackObject()); + this(1, 1, StaticFilters.FILTER_SPELL_OR_ABILITY); } public TargetStackObject(FilterStackObject filter) { @@ -59,7 +57,7 @@ public class TargetStackObject extends TargetObject { @Override public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { int count = 0; - for (StackObject stackObject: game.getStack()) { + for (StackObject stackObject : game.getStack()) { if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && filter.match(stackObject, sourceId, sourceControllerId, game)) { count++; if (count >= this.minNumberOfTargets) { @@ -78,7 +76,7 @@ public class TargetStackObject extends TargetObject { @Override public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { Set possibleTargets = new HashSet<>(); - for (StackObject stackObject: game.getStack()) { + for (StackObject stackObject : game.getStack()) { if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && filter.match(stackObject, sourceId, sourceControllerId, game)) { possibleTargets.add(stackObject.getId()); } diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index 7ebef50259..7b3af1ca0c 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -4,10 +4,7 @@ import mage.abilities.Ability; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; -import mage.target.targetpointer.FirstTargetPointer; -import mage.target.targetpointer.SecondTargetPointer; -import mage.target.targetpointer.TargetPointer; -import mage.target.targetpointer.ThirdTargetPointer; +import mage.target.targetpointer.*; import org.apache.log4j.Logger; import java.util.ArrayList; @@ -69,7 +66,7 @@ public class Targets extends ArrayList { if (!canChoose(source.getSourceId(), playerId, game)) { return false; } - int state = game.bookmarkState(); + //int state = game.bookmarkState(); while (!isChosen()) { Target target = this.getUnchosen().get(0); UUID targetController = playerId; @@ -96,7 +93,7 @@ public class Targets extends ArrayList { // Check if there are some rules for targets are violated, if so reset the targets and start again if (this.getUnchosen().isEmpty() && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source.getSourceId(), source.getControllerId()), source)) { - game.restoreState(state, "Targets"); + //game.restoreState(state, "Targets"); clearChosen(); } } @@ -170,9 +167,13 @@ public class Targets extends ArrayList { } } + if (targetPointer instanceof FixedTarget || targetPointer instanceof FixedTargets) { + // fixed target = direct ID, you can't find target type and description + proccessed = true; + } + if (!proccessed) { logger.error("Unknown target pointer " + (targetPointer != null ? targetPointer : "null"), new Throwable()); - // TODO: add other target types? } return null; diff --git a/Mage/src/main/java/mage/target/common/TargetOpponentOrPlaneswalker.java b/Mage/src/main/java/mage/target/common/TargetOpponentOrPlaneswalker.java index ac89c3f537..20f4fa974e 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponentOrPlaneswalker.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponentOrPlaneswalker.java @@ -8,23 +8,22 @@ package mage.target.common; import mage.filter.common.FilterOpponentOrPlaneswalker; /** - * * @author LevelX2 */ public class TargetOpponentOrPlaneswalker extends TargetPermanentOrPlayer { public TargetOpponentOrPlaneswalker() { - this(1, 1, new FilterOpponentOrPlaneswalker("opponent or planeswalker"), false); - } - - public TargetOpponentOrPlaneswalker(int numTargets) { - this(numTargets, numTargets, new FilterOpponentOrPlaneswalker(), false); + this(1); } public TargetOpponentOrPlaneswalker(FilterOpponentOrPlaneswalker filter) { this(1, 1, filter, false); } + public TargetOpponentOrPlaneswalker(int numTargets) { + this(numTargets, numTargets, new FilterOpponentOrPlaneswalker("opponent or planeswalker"), false); + } + public TargetOpponentOrPlaneswalker(int minNumTargets, int maxNumTargets, FilterOpponentOrPlaneswalker filter, boolean notTarget) { super(minNumTargets, maxNumTargets, filter, notTarget); } diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java index 70dab46b02..cee4973c4f 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java @@ -1,28 +1,26 @@ package mage.target.common; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.constants.Zone; import mage.filter.Filter; import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; import mage.filter.common.FilterPermanentOrPlayer; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetImpl; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author nantuko */ public class TargetPermanentOrPlayer extends TargetImpl { protected FilterPermanentOrPlayer filter; - protected FilterPermanent filterPermanent; public TargetPermanentOrPlayer() { this(1, 1); @@ -46,14 +44,12 @@ public class TargetPermanentOrPlayer extends TargetImpl { this.zone = Zone.ALL; this.filter = filter; this.targetName = filter.getMessage(); - this.filterPermanent = this.filter.getPermanentFilter(); this.notTarget = notTarget; } public TargetPermanentOrPlayer(final TargetPermanentOrPlayer target) { super(target); this.filter = target.filter.copy(); - this.filterPermanent = target.filterPermanent.copy(); } @Override @@ -61,10 +57,6 @@ public class TargetPermanentOrPlayer extends TargetImpl { return filter; } - public void setFilter(FilterPermanentOrPlayer filter) { - this.filter = filter; - } - @Override public boolean canTarget(UUID id, Game game) { Permanent permanent = game.getPermanent(id); @@ -118,7 +110,7 @@ public class TargetPermanentOrPlayer extends TargetImpl { * {@link mage.players.Player} that can be chosen. Should only be used for * Ability targets since this checks for protection, shroud etc. * - * @param sourceId - the target event source + * @param sourceId - the target event source * @param sourceControllerId - controller of the target event source * @param game * @return - true if enough valid {@link mage.game.permanent.Permanent} or @@ -130,14 +122,14 @@ public class TargetPermanentOrPlayer extends TargetImpl { MageObject targetSource = game.getObject(sourceId); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && player.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.getPlayerFilter().match(player, sourceId, sourceControllerId, game)) { + if (player != null && player.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(player, sourceId, sourceControllerId, game)) { count++; if (count >= this.minNumberOfTargets) { return true; } } } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { if (permanent.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(permanent, sourceId, sourceControllerId, game)) { count++; if (count >= this.minNumberOfTargets) { @@ -170,7 +162,7 @@ public class TargetPermanentOrPlayer extends TargetImpl { } } } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { if (filter.match(permanent, null, sourceControllerId, game) && filter.match(permanent, game)) { count++; if (count >= this.minNumberOfTargets) { @@ -187,11 +179,11 @@ public class TargetPermanentOrPlayer extends TargetImpl { MageObject targetSource = game.getObject(sourceId); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game)) && filter.getPlayerFilter().match(player, sourceId, sourceControllerId, game)) { + if (player != null && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game)) && filter.match(player, sourceId, sourceControllerId, game)) { possibleTargets.add(playerId); } } - for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterPermanent(), sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) && filter.match(permanent, sourceId, sourceControllerId, game)) { possibleTargets.add(permanent.getId()); } @@ -204,11 +196,11 @@ public class TargetPermanentOrPlayer extends TargetImpl { Set possibleTargets = new HashSet<>(); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && filter.getPlayerFilter().match(player, game)) { + if (player != null && filter.match(player, game)) { possibleTargets.add(playerId); } } - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { if (filter.match(permanent, null, sourceControllerId, game)) { possibleTargets.add(permanent.getId()); } @@ -237,6 +229,6 @@ public class TargetPermanentOrPlayer extends TargetImpl { } public FilterPermanent getFilterPermanent() { - return filterPermanent.copy(); + return filter.getPermanentFilter().copy(); } } diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerWithCounter.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerWithCounter.java deleted file mode 100644 index d6a515991d..0000000000 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerWithCounter.java +++ /dev/null @@ -1,87 +0,0 @@ - - -package mage.target.common; - -import mage.abilities.Ability; -import mage.filter.common.FilterPermanentOrPlayerWithCounter; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import java.util.UUID; -import mage.filter.FilterPermanent; -import mage.filter.predicate.permanent.CounterPredicate; - -/** - * - * @author nantuko - */ -public class TargetPermanentOrPlayerWithCounter extends TargetPermanentOrPlayer { - - protected final FilterPermanentOrPlayerWithCounter targetFilter; - - public TargetPermanentOrPlayerWithCounter() { - this(1, 1); - } - - public TargetPermanentOrPlayerWithCounter(int numTargets) { - this(numTargets, numTargets); - } - - public TargetPermanentOrPlayerWithCounter(int minNumTargets, int maxNumTargets) { - this(minNumTargets, maxNumTargets, false); - } - - public TargetPermanentOrPlayerWithCounter(int minNumTargets, int maxNumTargets, boolean notTarget) { - super(minNumTargets, maxNumTargets, notTarget); - this.targetFilter = new FilterPermanentOrPlayerWithCounter(); - this.filterPermanent = new FilterPermanent(); - this.filterPermanent.add(new CounterPredicate(null)); - this.targetName = targetFilter.getMessage(); - } - - public TargetPermanentOrPlayerWithCounter(final TargetPermanentOrPlayerWithCounter target) { - super(target); - this.targetFilter = target.targetFilter.copy(); - super.setFilter(this.targetFilter); - } - - @Override - public TargetPermanentOrPlayerWithCounter copy() { - return new TargetPermanentOrPlayerWithCounter(this); - } - - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - if (permanent.getCounters(game).isEmpty()) { - return false; - } - } - Player player = game.getPlayer(id); - if (player != null) { - if (player.getCounters().isEmpty()) { - return false; - } - } - return super.canTarget(id, game); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - if (permanent.getCounters(game).isEmpty()) { - return false; - } - } - Player player = game.getPlayer(id); - if (player != null) { - if (player.getCounters().isEmpty()) { - return false; - } - } - return super.canTarget(id, source, game); - } - -} diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java b/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java index c8a14e5c27..06a2eaf0e4 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTarget.java @@ -1,15 +1,17 @@ package mage.target.targetpointer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.cards.Card; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + public class FixedTarget implements TargetPointer { private final UUID targetId; @@ -21,6 +23,10 @@ public class FixedTarget implements TargetPointer { this.initialized = false; } + public FixedTarget(MageObjectReference mor) { + this(mor.getSourceId(), mor.getZoneChangeCounter()); + } + public FixedTarget(Card card, Game game) { this.targetId = card.getId(); this.zoneChangeCounter = card.getZoneChangeCounter(game); diff --git a/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java b/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java index 66a0b24ce0..e138db2787 100644 --- a/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java @@ -37,10 +37,12 @@ public class DragonOnTheBattlefieldWhileSpellWasCastWatcher extends Watcher { // revealed a Dragon card or controlled a Dragon as you cast the spell if (spell != null) { boolean revealedOrOnBattlefield = false; - for (Cost cost : spell.getSpellAbility().getCosts()) { - if (cost instanceof RevealTargetFromHandCost) { - revealedOrOnBattlefield = ((RevealTargetFromHandCost) cost).getNumberRevealedCards() > 0; - break; + if (spell.getSpellAbility() != null) { + for (Cost cost : spell.getSpellAbility().getCosts()) { + if (cost instanceof RevealTargetFromHandCost) { + revealedOrOnBattlefield = ((RevealTargetFromHandCost) cost).getNumberRevealedCards() > 0; + break; + } } } if (!revealedOrOnBattlefield) { diff --git a/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java b/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java new file mode 100644 index 0000000000..7c64308dd4 --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java @@ -0,0 +1,75 @@ +package mage.cards.decks.importer; + +import mage.cards.decks.DeckCardLists; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DraftLogImporterTest { + + private static final FakeCardLookup LOOKUP = new FakeCardLookup(); + + @Test + public void testImport() { + StringBuilder errors = new StringBuilder(); + DraftLogImporter importer = new DraftLogImporter() { + @Override + public CardLookup getCardLookup() { + return LOOKUP; + } + }; + DeckCardLists deck = importer.importDeck( + "src/test/java/mage/cards/decks/importer/samples/testdeck.draft", errors); + + TestDeckChecker.checker() + .addMain("Raging Ravine", 1) + .addMain("Fiery Temper", 1) + .addMain("Wild Mongrel", 1) + .addMain("Wild Mongrel", 1) + .addMain("Shielding Plax", 1) + .addMain("Wild Mongrel", 1) + .addMain("Basking Rootwalla", 1) + .addMain("Wild Mongrel", 1) + .addMain("Arena Athlete", 1) + .addMain("Undying Rage", 1) + .addMain("Molten Birth", 1) + .addMain("Shed Weakness", 1) + .addMain("Pulse of Murasa", 1) + .addMain("Just the Wind", 1) + .addMain("Stitcher's Apprentice", 1) + .addMain("Life from the Loam", 1) + .addMain("Satyr Wayfinder", 1) + .addMain("Mad Prophet", 1) + .addMain("Wild Mongrel", 1) + .addMain("Wickerbough Elder", 1) + .addMain("Basking Rootwalla", 1) + .addMain("Satyr Wayfinder", 1) + .addMain("Brawn", 1) + .addMain("Myr Servitor", 1) + .addMain("Terramorphic Expanse", 1) + .addMain("Foil", 1) + .addMain("Flight of Fancy", 1) + .addMain("Mark of the Vampire", 1) + .addMain("Repel the Darkness", 1) + .addMain("Golgari Charm", 1) + .addMain("Raid Bombardment", 1) + .addMain("Reckless Wurm", 1) + .addMain("Satyr Wayfinder", 1) + .addMain("Kodama's Reach", 1) + .addMain("Last Gasp", 1) + .addMain("Wild Mongrel", 1) + .addMain("Myr Servitor", 1) + .addMain("Raid Bombardment", 1) + .addMain("Treasure Cruise", 1) + .addMain("Bloodflow Connoisseur", 1) + .addMain("Treasure Cruise", 1) + .addMain("Hyena Umbra", 1) + .addMain("Kodama's Reach", 1) + .addMain("Just the Wind", 1) + .addMain("Flight of Fancy", 1) + .verify(deck, 45, 0); + + assertEquals("", errors.toString()); + } + +} \ No newline at end of file diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.draft b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.draft new file mode 100644 index 0000000000..95aaf72cae --- /dev/null +++ b/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.draft @@ -0,0 +1,466 @@ +Event #: 8a74113b-27e5-4a29-85be-4b83f622af00 +Time: 4/11/2019 8:55:15 AM +Players: + Computer Player 4 + Computer Player 2 + Computer Player 6 + hitchyflav + Computer Player 5 + Computer Player 3 + +------ UMA ------ + +Pack 1 pick 1: + Deranged Assistant + Nightbird's Clutches + Groundskeeper + Sanitarium Skeleton + Skywing Aven + Foil + Tethmos High Priest + Kodama's Reach + Pulse of Murasa + Basking Rootwalla + Ulamog's Crusher + Vengeful Rebirth + Laboratory Maniac + Boneyard Wurm +--> Raging Ravine + +Pack 1 pick 2: + Deranged Assistant + Olivia's Dragoon + Sultai Skullkeeper +--> Fiery Temper + Wild Mongrel + Dimir Guildmage + Turn to Mist + Ingot Chewer + Just the Wind + Mad Prophet + Rakdos Shred-Freak + Firewing Phoenix + Vengeful Rebirth + Fecundity + +Pack 1 pick 3: + Death Denied + Stitcher's Apprentice + Archaeomancer + Arena Athlete + Lotus-Eye Mystics +--> Wild Mongrel + Spider Umbra + Martyr of Sands + Mad Prophet + Ghoulcaller's Accomplice + Safehold Elite + Malevolent Whispers + Devoted Druid + +Pack 1 pick 4: + Stitcher's Apprentice +--> Wild Mongrel + Ronom Unicorn + Death Denied + Flight of Fancy + Undying Rage + Grave Scrabbler + Wandering Champion + Angelic Renewal + Patchwork Gnomes + Wingsteed Rider + Mage-Ring Network + +Pack 1 pick 5: + Defy Gravity + Grave Scrabbler + Fume Spitter +--> Shielding Plax + Ghoulcaller's Accomplice + Rakdos Shred-Freak + Sparkspitter + Resurrection + Molten Birth + Slum Reaper + Grave Strength + +Pack 1 pick 6: + Terramorphic Expanse +--> Wild Mongrel + Shed Weakness + Tethmos High Priest + Angelic Renewal + Slum Reaper + Foil + Rune Snag + Wickerbough Elder + Frantic Search + +Pack 1 pick 7: + Deranged Assistant + Nightbird's Clutches + Groundskeeper + Skywing Aven + Foil + Kodama's Reach + Pulse of Murasa +--> Basking Rootwalla + Vengeful Rebirth + +Pack 1 pick 8: + Deranged Assistant + Olivia's Dragoon + Sultai Skullkeeper +--> Wild Mongrel + Dimir Guildmage + Turn to Mist + Just the Wind + Rakdos Shred-Freak + +Pack 1 pick 9: + Death Denied + Stitcher's Apprentice + Archaeomancer +--> Arena Athlete + Spider Umbra + Martyr of Sands + Ghoulcaller's Accomplice + +Pack 1 pick 10: + Stitcher's Apprentice + Ronom Unicorn + Flight of Fancy +--> Undying Rage + Wandering Champion + Angelic Renewal + +Pack 1 pick 11: + Defy Gravity + Fume Spitter + Ghoulcaller's Accomplice +--> Molten Birth + Slum Reaper + +Pack 1 pick 12: +--> Shed Weakness + Angelic Renewal + Rune Snag + Frantic Search + +Pack 1 pick 13: + Foil + Kodama's Reach +--> Pulse of Murasa + +Pack 1 pick 14: + Sultai Skullkeeper +--> Just the Wind + +Pack 1 pick 15: +--> Stitcher's Apprentice + +------ UMA ------ + +Pack 2 pick 1: + Reckless Wurm + Dark Dabbling + Lotus-Eye Mystics + Heliod's Pilgrim + Satyr Wayfinder + Moan of the Unhallowed + Akroan Crusader + Fire // Ice + Grave Scrabbler + Mark of the Vampire + Safehold Elite + Blast of Genius + Spirit Cairn + Shriekmaw +--> Life from the Loam + +Pack 2 pick 2: +--> Satyr Wayfinder + Shed Weakness + Rakdos Shred-Freak + Defy Gravity + Crow of Dark Tidings + Frantic Search + Walker of the Grove + Prey Upon + Repel the Darkness + Eel Umbra + Brawn + Stingerfling Spider + Forbidden Alchemy + Celestial Colonnade + +Pack 2 pick 3: + Deranged Assistant + Beckon Apparition + Bloodflow Connoisseur + Faith's Fetters +--> Mad Prophet + Myr Servitor + Shed Weakness + Dimir Guildmage + Ingot Chewer + Icatian Crier + Plumeveil + Golgari Charm + Visions of Beyond + +Pack 2 pick 4: + Defy Gravity + Conviction + Terramorphic Expanse +--> Wild Mongrel + Moan of the Unhallowed + Gurmag Angler + Vessel of Endless Rest + Hissing Iguanar + Mark of the Vampire + Martyr of Sands + Circular Logic + Rise from the Tides + +Pack 2 pick 5: + Slum Reaper + Icatian Crier +--> Wickerbough Elder + Mad Prophet + Defy Gravity + Gurmag Angler + Archaeomancer + Foil + Eel Umbra + Vessel of Endless Rest + Rally the Peasants + +Pack 2 pick 6: + Wandering Champion +--> Basking Rootwalla + Frantic Search + Angelic Renewal + Cathodion + Olivia's Dragoon + Flight of Fancy + Skyspear Cavalry + Thermo-Alchemist + Fume Spitter + +Pack 2 pick 7: + Dark Dabbling + Lotus-Eye Mystics + Heliod's Pilgrim +--> Satyr Wayfinder + Moan of the Unhallowed + Akroan Crusader + Grave Scrabbler + Mark of the Vampire + Blast of Genius + +Pack 2 pick 8: + Shed Weakness + Defy Gravity + Crow of Dark Tidings + Frantic Search + Walker of the Grove + Repel the Darkness + Eel Umbra +--> Brawn + +Pack 2 pick 9: + Deranged Assistant + Beckon Apparition + Bloodflow Connoisseur +--> Myr Servitor + Shed Weakness + Icatian Crier + Golgari Charm + +Pack 2 pick 10: + Defy Gravity + Conviction +--> Terramorphic Expanse + Moan of the Unhallowed + Mark of the Vampire + Circular Logic + +Pack 2 pick 11: + Icatian Crier + Defy Gravity +--> Foil + Eel Umbra + Vessel of Endless Rest + +Pack 2 pick 12: + Wandering Champion + Angelic Renewal +--> Flight of Fancy + Fume Spitter + +Pack 2 pick 13: + Dark Dabbling + Moan of the Unhallowed +--> Mark of the Vampire + +Pack 2 pick 14: +--> Repel the Darkness + Eel Umbra + +Pack 2 pick 15: +--> Golgari Charm + +------ UMA ------ + +Pack 3 pick 1: + Myr Servitor + Dimir Guildmage +--> Raid Bombardment + Kodama's Reach + Arena Athlete + Martyr of Sands + Shed Weakness + Skywing Aven + Wickerbough Elder + Hooting Mandrills + Sparkspitter + Desperate Ritual + Grave Strength + Dawn Charm + Reya Dawnbringer + +Pack 3 pick 2: + Pulse of Murasa + Crow of Dark Tidings + Raid Bombardment +--> Reckless Wurm + Hyena Umbra + Just the Wind + Verdant Eidolon + Groundskeeper + Double Cleave + Repel the Darkness + Stitcher's Apprentice + Sleight of Hand + Living Lore + Artisan of Kozilek + +Pack 3 pick 3: + Moan of the Unhallowed + Thermo-Alchemist + Stitcher's Apprentice + Bloodflow Connoisseur + Sparkspitter + Mark of the Vampire + Treasure Cruise + Flight of Fancy + Unholy Hunger + Staunch-Hearted Warrior +--> Satyr Wayfinder + Spirit Cairn + Blast of Genius + +Pack 3 pick 4: + Bloodflow Connoisseur + Grave Scrabbler + Hyena Umbra + Slum Reaper +--> Kodama's Reach + Sparkspitter + Sanitarium Skeleton + Stitched Drake + Pulse of Murasa + Mammoth Umbra + Olivia's Dragoon + Vengeful Rebirth + +Pack 3 pick 5: + Beckon Apparition + Turn to Mist + Spider Umbra + Bloodflow Connoisseur +--> Last Gasp + Terramorphic Expanse + Treasure Cruise + Miming Slime + Emancipation Angel + Warleader's Helix + Snapcaster Mage + +Pack 3 pick 6: + Ingot Chewer + Defy Gravity + Mammoth Umbra +--> Wild Mongrel + Reckless Charge + Fume Spitter + Skywing Aven + Hyena Umbra + Twins of Maurer Estate + Urban Evolution + +Pack 3 pick 7: +--> Myr Servitor + Kodama's Reach + Arena Athlete + Martyr of Sands + Shed Weakness + Skywing Aven + Wickerbough Elder + Sparkspitter + Dawn Charm + +Pack 3 pick 8: + Pulse of Murasa +--> Raid Bombardment + Hyena Umbra + Just the Wind + Repel the Darkness + Stitcher's Apprentice + Sleight of Hand + Living Lore + +Pack 3 pick 9: + Moan of the Unhallowed + Thermo-Alchemist + Stitcher's Apprentice + Sparkspitter + Mark of the Vampire +--> Treasure Cruise + Flight of Fancy + +Pack 3 pick 10: +--> Bloodflow Connoisseur + Hyena Umbra + Sanitarium Skeleton + Stitched Drake + Mammoth Umbra + Olivia's Dragoon + +Pack 3 pick 11: + Spider Umbra + Terramorphic Expanse +--> Treasure Cruise + Miming Slime + Warleader's Helix + +Pack 3 pick 12: + Defy Gravity + Skywing Aven +--> Hyena Umbra + Urban Evolution + +Pack 3 pick 13: +--> Kodama's Reach + Martyr of Sands + Shed Weakness + +Pack 3 pick 14: + Hyena Umbra +--> Just the Wind + +Pack 3 pick 15: +--> Flight of Fancy + diff --git a/Utils/gen-card.pl b/Utils/gen-card.pl index 44abc4a54c..3c2e34b1c2 100755 --- a/Utils/gen-card.pl +++ b/Utils/gen-card.pl @@ -32,7 +32,7 @@ sub fixCost { my $author; if (-e $authorFile) { - open (DATA, $authorFile); + open (DATA, $authorFile) || die "can't open $authorFile : $!"; $author = ; chomp $author; close(DATA); @@ -40,14 +40,14 @@ if (-e $authorFile) { $author = 'anonymous'; } -open (DATA, $dataFile) || die "can't open $dataFile"; +open (DATA, $dataFile) || die "can't open $dataFile : $!"; while(my $line = ) { my @data = split('\\|', $line); $cards{$data[0]}{$data[1]} = \@data; } close(DATA); -open (DATA, $setsFile) || die "can't open $setsFile"; +open (DATA, $setsFile) || die "can't open $setsFile : $!"; while(my $line = ) { my @data = split('\\|', $line); $sets{$data[0]}= $data[1]; @@ -55,14 +55,14 @@ while(my $line = ) { } close(DATA); -open (DATA, $knownSetsFile) || die "can't open $knownSetsFile"; +open (DATA, $knownSetsFile) || die "can't open $knownSetsFile : $!"; while(my $line = ) { my @data = split('\\|', $line); $knownSets{$data[0]}= $data[1]; } close(DATA); -open (DATA, $keywordsFile) || die "can't open $keywordsFile"; +open (DATA, $keywordsFile) || die "can't open $keywordsFile : $!"; while(my $line = ) { my @data = split('\\|', $line); $keywords{toCamelCase($data[0])}= $data[1]; diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index d9c7e2086a..334185d784 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -34851,47 +34851,98 @@ Domri's Nodorog|Ravnica Allegiance|272|R|{3}{R}{G}|Creature - Beast|5|2|Trample$ The Haunt of Hightower|Ravnica Allegiance|273|M|{4}{B}{B}|Legendary Creature - Vampire|3|3|Flying, lifelink$Whenever The Haunt of Hightower attacks, defending player discards a card.$Whenever a card is put into an opponent's graveyard from anywhere, put a +1/+1 counter on The Haunt of Hightower.| Serra the Benevolent|Modern Horizons|26|M|{2}{W}{W}|Legendary Planeswalker - Serra|4|+2: Creatures you control with flying get +1/+1 until end of turn.$-3: Create a 4/4 white Angel creature token with flying and vigilance.$-6: You get an emblem with "If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead."| Cabal Therapist|Modern Horizons|80|R|{B}|Creature - Horror|1|1|Menace$At the beginning of your precombat main phase, you may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name.| +Karn, the Great Creator|War of the Spark|1|R|{4}|Legendary Planeswalker - Karn|5|Activated abilities of artifacts your opponents control can't be activated.$+1: Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness each equal to its converted mana cost.$-2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand.| +Ugin, the Ineffable|War of the Spark|2|R|{6}|Legendary Planeswalker - Ugin|4|Colorless spells you cast cost {2} less to cast.$+1: Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand.$-3: Destroy target permanent that's one or more colors.| +Ugin's Conjurant|War of the Spark|3|U|{X}|Creature - Spirit Monk|0|0|Ugin's Conjurant enters the battlefield with X +1/+1 counters on it.$If damage would be dealt to Ugin's Conjurant while it has a +1/+1 counter on it, prevent that damage and remove that many +1/+1 counters from Ugin's Conjurant.| Ajani's Pridemate|War of the Spark|4|U|{1}{W}|Creature - Cat Soldier|2|2|Whenever you gain life, put a +1/+1 counter on Ajani's Pridemate.| +Battlefield Promotion|War of the Spark|5|C|{1}{W}|Instant|||Put a +1/+1 counter on target creature. That creature gains first strike until end of turn. You gain 2 life.| +Bond of Discipline|War of the Spark|6|U|{4}{W}|Sorcery|||Tap all creatures your opponents control. Creatures you control gain lifelink until end of turn.| Bulwark Giant|War of the Spark|7|C|{5}{W}|Creature - Giant Soldier|3|6|When Bulwark Giant enters the battlefield, you gain 5 life.| +Charmed Stray|War of the Spark|8|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.| Defiant Strike|War of the Spark|9|C|{W}|Instant|||Target creature gets +1/+0 until end of turn.$Draw a card.| +Divine Arrow|War of the Spark|10|C|{1}{W}|Instant|||Divine Arrow deals 4 damage to target attacking or blocking creature.| +Enforcer Griffin|War of the Spark|11|C|{4}{W}|Creature - Griffin|3|4|Flying| +Finale of Glory|War of the Spark|12|M|{X}{W}{W}|Sorcery|||Create X 2/2 white Soldier creature tokens with vigilance. If X is 10 or more, also create X 4/4 white Angel creature tokens with flying and vigilance.| +Gideon Blackblade|War of the Spark|13|M|{1}{W}{W}|Legendary Planeswalker - Gideon|4|As long as it's your turn, Gideon Blackblade is a 4/4 Human Soldier creature with indestructible that's still a planeswalker.$Prevent all damage that would be dealt to Gideon Blackblade during your turn.$+1: Up to one other target creature you control gains your choice of vigilance, lifelink, or indestructible until end of turn.$-6: Exile target nonland permanent.| +Gideon's Sacrifice|War of the Spark|14|C|{W}|Instant|||Choose a creature or planeswalker you control. All damage that would be dealt this turn to you and permanents you control is dealt to the chosen permanent instead.| Gideon's Triumph|War of the Spark|15|U|{1}{W}|Instant|||Target opponent sacrifices a creature that attacked or blocked this turn. If you control a Gideon planeswalker, that player sacrifices two of those creatures instead.| +God-Eternal Oketra|War of the Spark|16|M|{3}{W}{W}|Legendary Creature - Zombie God|3|6|Double strike$Whenever you cast a creature spell, create a 4/4 black Zombie Warrior creature token with vigilance.$When God-Eternal Oketra dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| Grateful Apparition|War of the Spark|17|U|{1}{W}|Creature - Spirit|1|1|Flying$Whenever Grateful Apparition deals combat damage to a player or planeswalker, proliferate.| Ignite the Beacon|War of the Spark|18|R|{4}{W}|Instant|||Search your library for up to two planeswalker cards, reveal them, put them into your hand, then shuffle your library.| +Ironclad Krovod|War of the Spark|19|C|{3}{W}|Creature - Beast|2|5|| +Law-Rune Enforcer|War of the Spark|20|C|{W}|Creature - Human Soldier|1|2|{1}, {T}: Tap target creature with converted mana cost 2 or greater.| Loxodon Sergeant|War of the Spark|21|C|{3}{W}|Creature - Elephant Soldier|3|3|Vigilance$When Loxodon Sergeant enters the battlefield, other creatures you control gain vigilance until end of turn.| Makeshift Battalion|War of the Spark|22|C|{2}{W}|Creature - Human Soldier|3|2|Whenever Makeshift Battalion and at least two other creatures attack, put a +1/+1 counter on Makeshift Battalion.| +Martyr for the Cause|War of the Spark|23|C|{1}{W}|Creature - Human Soldier|2|2|When Martyr for the Cause dies, proliferate.| +Parhelion II|War of the Spark|24|R|{6}{W}{W}|Legendary Artifact - Vehicle|5|5|Flying, first strike, vigilance$Whenever Parhelion II attacks, create two 4/4 white Angel creature tokens with flying and vigilance that are attacking.$Crew 4| Pouncing Lynx|War of the Spark|25|C|{1}{W}|Creature - Cat|2|1|As long as it's your turn, Pouncing Lynx has first strike.| +Prison Realm|War of the Spark|26|U|{2}{W}|Enchantment|||When Prison Realm enters the battlefield, exile target creature or planeswalker an opponent controls until Prison Realm leaves the battlefield.$When Prison Realm enters the battlefield, scry 1.| +Rally of Wings|War of the Spark|27|U|{1}{W}|Instant|||Untap all creatures you control. Creatures you control with flying get +2/+2 until end of turn.| Ravnica at War|War of the Spark|28|R|{3}{W}|Sorcery|||Exile all multicolored permanents.| Rising Populace|War of the Spark|29|C|{2}{W}|Creature - Human|2|2|Whenever another creature or planeswalker you control dies, put a +1/+1 counter on Rising Populace.| Single Combat|War of the Spark|30|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.| +Sunblade Angel|War of the Spark|31|U|{5}{W}|Creature - Angel|3|3|Flying, first strike, vigilance, lifelink| Teyo, the Shieldmage|War of the Spark|32|U|{2}{W}|Legendary Planeswalker - Teyo|5|You have hexproof.$-2: Create a 0/3 white Wall creature token with defender.| Teyo's Lightshield|War of the Spark|33|C|{2}{W}|Creature - Illusion|0|3|When Teyo's Lightshield enters the battlefield, put a +1/+1 counter on target creature you control.| Tomik, Distinguished Advokist|War of the Spark|34|R|{W}{W}|Legendary Creature - Human Advisor|2|3|Flying$Lands on the battlefield and land cards in graveyards can't be the targets of spells or abilities your opponents control.$Your opponents can't play land cards from graveyards.| +Topple the Statue|War of the Spark|35|C|{2}{W}|Instant|||Tap target permanent. If it's an artifact, destroy it.$Draw a card.| +Trusted Pegasus|War of the Spark|36|C|{2}{W}|Creature - Pegasus|2|2|Flying$Whenever Trusted Pegasus attacks, target attacking creature without flying gains flying until end of turn.| The Wanderer|War of the Spark|37|U|{3}{W}|Legendary Planeswalker|5|Prevent all noncombat damage that would be dealt to you and other permanents you control.$-2: Exile target creature with power 4 or greater.| Wanderer's Strike|War of the Spark|38|C|{4}{W}|Sorcery|||Exile target creature, then proliferate.| War Screecher|War of the Spark|39|C|{1}{W}|Creature - Bird|1|3|Flying${5}{W}, {T}: Other creatures you control get +1/+1 until end of turn.| +Ashiok's Skulker|War of the Spark|40|C|{4}{U}|Creature - Nightmare|3|5|{3}{U}: Ashiok's Skulker can't be blocked this turn.| Augur of Bolas|War of the Spark|41|U|{1}{U}|Creature - Merfolk Wizard|1|3|When Augur of Bolas enters the battlefield, look at the top three cards of your library. You may reveal an instant or sorcery card from among them and put it into your hand. Put the rest on the bottom of your library in any order.| +Aven Eternal|War of the Spark|42|C|{2}{U}|Creature - Zombie Bird Warrior|2|2|Flying$When Aven Eternal enters the battlefield, amass 1.| +Bond of Insight|War of the Spark|43|U|{3}{U}|Sorcery|||Each player puts the top four cards of their library into their graveyard. Return up to two instant and/or sorcery cards from your graveyard to your hand. Exile Bond of Insight.| +Callous Dismissal|War of the Spark|44|C|{1}{U}|Sorcery|||Return target nonland permanent to its owner's hand.$Amass 1.| +Commence the Endgame|War of the Spark|45|R|{4}{U}{U}|Instant|||This spell can't be countered.$Draw two cards, then amass X, where X is the number of cards in your hand.| +Contentious Plan|War of the Spark|46|C|{1}{U}|Sorcery|||Proliferate.$Draw a card.| Crush Dissent|War of the Spark|47|C|{3}{U}|Instant|||Counter target spell unless its controller pays {2}.$Amass 2.| Erratic Visionary|War of the Spark|48|C|{1}{U}|Creature - Human Wizard|1|3|{1}{U}, {T}: Draw a card, then discard a card.| +Eternal Skylord|War of the Spark|49|U|{4}{U}|Creature - Zombie Wizard|3|3|When Eternal Skylord enters the battlefield, amass 2.$Zombie tokens you control have flying.| Fblthp, the Lost|War of the Spark|50|R|{1}{U}|Legendary Creature - Homunculus|1|1|When Fblthp, the Lost enters the battlefield, draw a card. If it entered from your library or was cast from your library, draw two cards instead.$When Fblthp becomes the target of a spell, shuffle Fblthp into its owner's library.| +Finale of Revelation|War of the Spark|51|M|{X}{U}{U}|Sorcery|||Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.$Exile Finale of Revelation.| Flux Channeler|War of the Spark|52|U|{2}{U}|Creature - Human Wizard|2|2|Whenever you cast a noncreature spell, proliferate.| +God-Eternal Kefnet|War of the Spark|53|M|{2}{U}{U}|Legendary Creature - Zombie God|4|5|Flying$You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast.$When God-Eternal Kefnet dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| Jace, Wielder of Mysteries|War of the Spark|54|R|{1}{U}{U}{U}|Legendary Planeswalker - Jace|4|If you would draw a card while your library has no cards in it, you win the game instead.$+1: Target player puts the top two cards of their library into their graveyard. Draw a card.$-8: Draw seven cards. Then if your library has no cards in it, you win the game.| +Jace's Triumph|War of the Spark|55|U|{2}{U}|Sorcery|||Draw two cards. If you control a Jace planeswalker, draw three cards instead.| +Kasmina, Enigmatic Mentor|War of the Spark|56|U|{3}{U}|Legendary Planeswalker - Kasmina|5|Spells your opponents cast that target a creature or planeswalker you control cost {2} more to cast.$-2: Create a 2/2 blue Wizard creature token. Draw a card, then discard a card.| +Kasmina's Transmutation|War of the Spark|57|C|{1}{U}|Enchantment - Aura|||Enchant creature$Enchanted creature loses all abilities and has base power and toughness 1/1.| Kiora's Dambreaker|War of the Spark|58|C|{5}{U}|Creature - Leviathan|5|6|When Kiora's Dambreaker enters the battlefield, proliferate.| Lazotep Plating|War of the Spark|59|U|{1}{U}|Instant|||Amass 1.$You and permanents you control gain hexproof until end of turn.| Naga Eternal|War of the Spark|60|C|{2}{U}|Creature - Zombie Naga|3|2|| +Narset, Parter of Veils|War of the Spark|61|U|{1}{U}{U}|Legendary Planeswalker - Narset|5|Each opponent can't draw more than one card each turn.$-2: Look at the top four cards of your library. You may reveal a noncreature, nonland card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.| +Narset's Reversal|War of the Spark|62|R|{U}{U}|Instant|||Copy target instant or sorcery spell, then return it to its owner's hand. You may choose new targets for the copy.| No Escape|War of the Spark|63|C|{2}{U}|Instant|||Counter target creature or planeswalker spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard.$Scry 1.| Relentless Advance|War of the Spark|64|C|{3}{U}|Sorcery|||Amass 3.| Rescuer Sphinx|War of the Spark|65|U|{2}{U}{U}|Creature - Sphinx|3|2|Flying$As Rescuer Sphinx enters the battlefield, you may return a nonland permanent you control to its owner's hand. If you do, Rescuer Sphinx enters the battlefield with a +1/+1 counter on it.| +Silent Submersible|War of the Spark|66|R|{U}{U}|Artifact - Vehicle|2|3|Whenever Silent Submersible deals combat damage to a player or planeswalker, draw a card.$Crew 2| +Sky Theater Strix|War of the Spark|67|C|{1}{U}|Creature - Bird|1|2|Flying$Whenever you cast a noncreature spell, Sky Theater Strix gets +1/+0 until end of turn.| +Spark Double|War of the Spark|68|R|{3}{U}|Creature - Illusion|0|0|You may have Spark Double enter the battlefield as a copy of a creature or planeswalker you control, except it enters with an additional +1/+1 counter on it if it's a creature, it enters with an additional loyalty counter on it if it's a planeswalker, and it isn't legendary if that permanent is legendary.| Spellkeeper Weird|War of the Spark|69|C|{2}{U}|Creature - Weird|1|4|{2}, {T}, Sacrifice Spellkeeper Weird: Return target instant or sorcery card from your graveyard to your hand.| Stealth Mission|War of the Spark|70|C|{2}{U}|Sorcery|||Put two +1/+1 counters on target creature you control. That creature can't be blocked this turn.| +Tamiyo's Epiphany|War of the Spark|71|C|{3}{U}|Sorcery|||Scry 4, then draw two cards.| Teferi's Time Twist|War of the Spark|72|C|{1}{U}|Instant|||Exile target permanent you control. Return that card to the battlefield under its owner's control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.| +Thunder Drake|War of the Spark|73|C|{3}{U}|Creature - Elemental Drake|2|3|Flying$Whenever you cast you cast your second spell each turn, put a +1/+1 counter on Thunder Drake.| Totally Lost|War of the Spark|74|C|{4}{U}|Instant|||Put target nonland permanent on top of its owner's library.| +Wall of Runes|War of the Spark|75|C|{U}|Creature - Wall|0|4|Defender$When Wall of Runes enters the battlefield, scry 1.| Aid the Fallen|War of the Spark|76|C|{1}{B}|Sorcery|||Choose one or both—$• Return target creature card from your graveyard to your hand.$• Return target planeswalker card from your graveyard to your hand.| Banehound|War of the Spark|77|C|{B}|Creature - Nightmare Hound|1|1|Lifelink, haste| +Bleeding Edge|War of the Spark|78|U|{1}{B}{B}|Sorcery|||Up to one target creature gets -2/-2 until end of turn. Amass 2.| Bolas's Citadel|War of the Spark|79|R|{3}{B}{B}{B}|Legendary Artifact|||You may look at the top card of your library any time.$You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.${T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.| +Bond of Revival|War of the Spark|80|U|{4}{B}|Sorcery|||Return target creature card from your graveyard to the battlefield. It gains haste until your next turn.| +Charity Extractor|War of the Spark|81|C|{3}{B}|Creature - Human Knight|1|5|Lifelink| +Command the Dreadhorde|War of the Spark|82|R|{4}{B}{B}|Sorcery|||Choose any number of target creature and/or planeswalker cards in graveyards. Command the Dreadhorde deals damage to you equal to the total converted mana cost of those cards. Put them onto the battlefield under your control.| Davriel, Rogue Shadowmage|War of the Spark|83|U|{2}{B}|Legendary Planeswalker - Davriel|3|At the beginning of each opponent's upkeep, if that player has one or fewer cards in hand, Davriel, Rogue Shadowmage deals 2 damage to them.$-1: Target player discards a card.| Davriel's Shadowfugue|War of the Spark|84|C|{3}{B}|Sorcery|||Target player discards two cards and loses 2 life.| +Deliver Unto Evil|War of the Spark|85|R|{2}{B}|Sorcery|||Choose up to four target cards in your graveyard. If you control a Bolas planeswalker, return those cards to your hand. Otherwise, an opponent chooses two of them. Leave the chosen cards in your graveyard and put the rest into your hand.$Exile Deliver Unto Evil.| Dreadhorde Invasion|War of the Spark|86|R|{1}{B}|Enchantment|||At the beginning of your upkeep, you lose 1 life and amass 1.$Whenever a Zombie token you control with power 6 or greater attacks, it gains lifelink until end of turn.| +Dreadmalkin|War of the Spark|87|U|{B}|Creature - Zombie Cat|1|1|Menace${2}{B}, Sacrifice another creature or planeswalker: Put two +1/+1 counters on Dreadmalkin.| +Duskmantle Operative|War of the Spark|88|C|{1}{B}|Creature - Human Rogue|2|2|Duskmantle Operative can't be blocked by creatures with power 4 or greater.| +The Elderspell|War of the Spark|89|R|{B}{B}|Sorcery|||Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way.| Eternal Taskmaster|War of the Spark|90|U|{1}{B}|Creature - Zombie|2|3|Eternal Taskmaster enters the battlefield tapped.$Whenever Eternal Taskmaster attacks, you may pay {2}{B}. If you do, return target creature card from your graveyard to your hand.| +Finale of Eternity|War of the Spark|91|M|{X}{B}{B}|Sorcery|||Destroy up to three target creatures with toughness X or less. If X is 10 or more, return all creature cards from your graveyard to the battlefield.| +God-Eternal Bontu|War of the Spark|92|M|{3}{B}{B}|Legendary Creature - Zombie God|5|6|Menace$When God-Eternal Bontu enters the battlefield, sacrifice any number of other permanents, then draw that many cards.$When God-Eternal Bontu dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| Herald of the Dreadhorde|War of the Spark|93|C|{3}{B}|Creature - Zombie Warrior|3|2|When Herald of the Dreadhorde dies, amass 2.| Kaya's Ghostform|War of the Spark|94|C|{B}|Enchantment - Aura|||Enchant creature or planeswalker you control$When enchanted permanent dies or is put into exile, return that card to the battlefield under your control.| Lazotep Behemoth|War of the Spark|95|C|{4}{B}|Creature - Zombie Hippo|5|4|| @@ -34901,72 +34952,151 @@ Liliana's Triumph|War of the Spark|98|U|{1}{B}|Instant|||Each opponent sacrifice Massacre Girl|War of the Spark|99|R|{3}{B}{B}|Legendary Creature - Human Assassin|4|4|Menace$When Massacre Girl enters the battlefield, each other creature gets -1/-1 until end of turn. Whenever a creature dies this turn, each creature other than Massacre Girl gets -1/-1 until end of turn.| Ob Nixilis, the Hate-Twisted|War of the Spark|100|U|{3}{B}{B}|Legendary Planeswalker - Nixilis|5|Whenever an opponent draws a card, Ob Nixilis, the Hate-Twisted deals 1 damage to that player.$-2: Destroy target creature. Its controller draws two cards.| Ob Nixilis's Cruelty|War of the Spark|101|C|{2}{B}|Instant|||Target creature gets -5/-5 until end of turn. If that creature would die this turn, exile it instead.| +Price of Betrayal|War of the Spark|102|U|{B}|Sorcery|||Remove up to five counters from target artifact, creature, planeswalker, or opponent.| +Shriekdiver|War of the Spark|103|C|{2}{B}|Creature - Zombie Bird Warrior|2|1|Flying${1}: Shriekdiver gains haste until end of turn.| Sorin's Thirst|War of the Spark|104|C|{B}{B}|Instant|||Sorin's Thirst deals 2 damage to target creature and you gain 2 life.| +Spark Harvest|War of the Spark|105|C|{B}|Sorcery|||As an additional cost to cast this spell, sacrifice a creature or pay {3}{B}.$Destroy target creature or planeswalker.| +Spark Reaper|War of the Spark|106|C|{2}{B}|Creature - Zombie|2|3|{3}, Sacrifice a creature or planeswalker: You gain 1 life and draw a card.| +Tithebearer Giant|War of the Spark|107|C|{5}{B}|Creature - Giant Warrior|4|5|When Tithebearer Giant enters the battlefield, you draw a card and you lose 1 life.| +Toll of the Invasion|War of the Spark|108|C|{2}{B}|Sorcery|||Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.$Amass 1.| +Unlikely Aid|War of the Spark|109|C|{1}{B}|Instant|||Target creature gets +2/+0 and gains indestructible until end of turn.| +Vampire Opportunist|War of the Spark|110|C|{1}{B}|Creature - Vampire|2|1|{6}{B}: Each opponent loses 2 life and you gain 2 life.| +Vizier of the Scorpion|War of the Spark|111|U|{2}{B}|Creature - Zombie Wizard|1|1|When Vizier of the Scorpion enters the battlefield, amass 1.$Zombie tokens you control have deathtouch.| Vraska's Finisher|War of the Spark|112|C|{2}{B}|Creature - Gorgon Assassin|3|2|When Vraska's Finisher enters the battlefield, destroy target creature or planeswalker an opponent controls that was dealt damage this turn.| Ahn-Crop Invader|War of the Spark|113|C|{2}{R}|Creature - Zombie Minotaur Warrior|2|2|As long as it's your turn, Ahn-Crop Invader has first strike.${1}, Sacrifice another creature: Ahn-Crop Invader gets +2/+0 until end of turn.| Blindblast|War of the Spark|114|C|{2}{R}|Instant|||Blindblast deals 1 damage to target creature. That creature can't block this turn.$Draw a card.| +Bolt Bend|War of the Spark|115|U|{3}{R}|Instant|||This spell costs {3} less to cast if you control a creature with power 4 or greater.$Change the target of target spell or ability with a single target.| +Bond of Passion|War of the Spark|116|U|{4}{R}{R}|Sorcery|||Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. Bond of Passion deals 2 damage to any other target.| Burning Prophet|War of the Spark|117|C|{1}{R}|Creature - Human Wizard|1|3|Whenever you cast a noncreature spell, Burning Prophet gets +1/+0 until end of turn, then scry 1.| +Chainwhip Cyclops|War of the Spark|118|C|{4}{R}|Creature - Cyclops Warrior|4|4|{3}{R}: Target creature can't block this turn.| +Chandra, Fire Artisan|War of the Spark|119|R|{2}{R}{R}|Legendary Planeswalker - Chandra|4|Whenever one or more loyalty counters are removed from Chandra, Fire Artisan, she deals that much damage to target opponent or planeswalker.$+1: Exile the top card of your library. You may play it this turn.$-7: Exile the top seven cards of your library. You may play them this turn.| +Chandra's Pyrohelix|War of the Spark|120|C|{1}{R}|Instant|||Chandra's Pyrohelix deals 2 damage divided as you choose among one or two targets.| +Chandra's Triumph|War of the Spark|121|U|{1}{R}|Instant|||Chandra's Triumph deals 3 damage to target creature or planeswalker an opponent controls. Chandra's Triumph deals 5 damage to that permanent instead if you control a Chandra planeswalker.| Cyclops Electromancer|War of the Spark|122|U|{4}{R}|Creature - Cyclops Wizard|4|2|When Cyclops Electromancer enters the battlefield, it deals X damage to target creature an opponent controls, where X is the number of instant and sorcery cards in your graveyard.| +Demolish|War of the Spark|123|C|{3}{R}|Sorcery|||Destroy target artifact or land.| Devouring Hellion|War of the Spark|124|U|{2}{R}|Creature - Hellion|2|2|As Devouring Hellion enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers. If you do, it enters with twice that many +1/+1 counters on it.| Dreadhorde Arcanist|War of the Spark|125|R|{1}{R}|Creature - Zombie Wizard|1|3|Trample$Whenever Dreadhorde Arcanist attacks, you may cast target instant or sorcery card with converted mana cost less than or equal to Dreadhorde Arcanist's power from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead.| +Dreadhorde Twins|War of the Spark|126|U|{3}{R}|Creature - Zombie Jackal Warrior|2|2|When Dreadhorde Twins enters the battlefield, amass 2.$Zombie tokens you control have trample.| +Finale of Promise|War of the Spark|127|M|{X}{R}{R}|Sorcery|||You may cast up to one target instant card and/or sorcery card from your graveyard each with converted mana cost X or less without paying their mana costs. If a card cast this way would be put into your graveyard this turn, exile it instead. If X is 10 or more, copy each of those spells twice. You may choose new targets for the copies.| Goblin Assailant|War of the Spark|128|C|{1}{R}|Creature - Goblin Warrior|2|2|| Goblin Assault Team|War of the Spark|129|C|{3}{R}|Creature - Goblin Warrior|4|1|Haste$When Goblin Assault Team dies, put a +1/+1 counter on target creature you control.| Grim Initiate|War of the Spark|130|C|{R}|Creature - Zombie Warrior|1|1|First strike$When Grim Initiate dies, amass 1.| Heartfire|War of the Spark|131|C|{1}{R}|Instant|||As an additional cost to cast this spell, sacrifice a creature or planeswalker.$Heartfire deals 4 damage to any target.| Honor the God-Pharaoh|War of the Spark|132|C|{2}{R}|Sorcery|||As an additional cost to cast this spell, discard a card.$Draw two cards. Amass 1.| +Ilharg, the Raze-Boar|War of the Spark|133|M|{3}{R}{R}|Legendary Creature - Boar God|6|6|Trample$Whenever Ilharg, the Raze-Boar attacks, you may put a creature card from your hand onto the battlefield tapped and attacking. Return that creature to your hand at the beginning of the next end step.$When Ilharg, the Raze-Boar dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| Invading Manticore|War of the Spark|134|C|{5}{R}|Creature - Zombie Manticore|4|5|When Invading Manticore enters the battlefield, amass 2.| +Jaya, Venerated Firemage|War of the Spark|135|U|{4}{R}|Legendary Planeswalker - Jaya|5|If another red source you control would deal damage to a permanent or player, it deals that much damage plus 1 to that permanent or player instead.$-2: Jaya, Venerated Firemage deals 2 damage to any target.| +Jaya's Greeting|War of the Spark|136|C|{1}{R}|Instant|||Jaya's Greeting deals 3 damage to target creature. Scry 1.| Krenko, Tin Street Kingpin|War of the Spark|137|R|{2}{R}|Legendary Creature - Goblin|1|2|Whenever Krenko, Tin Street Kingpin attacks, put a +1/+1 counter on it, then create a number of 1/1 red Goblin creature tokens equal to Krenko's power.| Mizzium Tank|War of the Spark|138|R|{1}{R}{R}|Artifact - Vehicle|3|2|Trample$Whenever you cast a noncreature spell, Mizzium Tank becomes an artifact creature and gets +1/+1 until end of turn.$Crew 1| Nahiri's Stoneblades|War of the Spark|139|C|{1}{R}|Instant|||Up to two target creatures each get +2/+0 until end of turn.| Neheb, Dreadhorde Champion|War of the Spark|140|R|{2}{R}{R}|Legendary Creature - Zombie Minotaur Warrior|5|4|Trample$Whenever Neheb, Dreadhorde Champion deals combat damage to a player or planeswalker, you may discard any number of cards. If you do, draw that many cards and add that much {R}. Until end of turn, you don't lose this mana as steps and phases end.| +Raging Kronch|War of the Spark|141|C|{2}{R}|Creature - Beast|4|3|Raging Kronch can't attack alone.| Samut's Sprint|War of the Spark|142|C|{R}|Instant|||Target creature gets +2/+1 and gains haste until end of turn. Scry 1.| +Sarkhan the Masterless|War of the Spark|143|R|{3}{R}{R}|Legendary Planeswalker - Sarkhan|5|Whenever a creature attacks you or a planeswalker you control, each Dragon you control deals 1 damage to that creature.$+1: Until end of turn, each planeswalker you control becomes a 4/4 red Dragon creature and gains flying.$-3: Create a 4/4 red Dragon creature token with flying.| +Sarkhan's Catharsis|War of the Spark|144|C|{4}{R}|Instant|||Sarkhan's Catharsis deals 5 damage to target player or planeswalker.| Spellgorger Weird|War of the Spark|145|C|{2}{R}|Creature - Weird|2|2|Whenever you cast a noncreature spell, put a +1/+1 counter on Spellgorger Weird.| Tibalt, Rakish Instigator|War of the Spark|146|U|{2}{R}|Legendary Planeswalker - Tibalt|5|Your opponents can't gain life.$-2: Create a 1/1 red Devil creature token with "Whenever this creature dies, it deals 1 damage to any target."| Tibalt's Rager|War of the Spark|147|U|{1}{R}|Creature - Devil|1|2|When Tibalt's Rager dies, it deals 1 damage to any target.${1}{R}: Tibalt's Rager gets +2/+0 until end of turn.| +Turret Ogre|War of the Spark|148|C|{3}{R}|Creature - Ogre Warrior|4|3|Reach$When Turret Ogre enters the battlefield, if you control another creature with power 4 or greater, Turret Ogre deals 2 damage to each opponent.| +Arboreal Grazer|War of the Spark|149|C|{G}|Creature - Beast|0|3|Reach$When Arboreal Grazer enters the battlefield, you may put a land card from your hand onto the battlefield tapped.| Arlinn, Voice of the Pack|War of the Spark|150|U|{4}{G}{G}|Legendary Planeswalker - Arlinn|7|Each creature you control that's a Wolf or Werewolf enters the battlefield with an additional +1/+1 counter on it.$-2: Create a 2/2 green Wolf creature token.| Arlinn's Wolf|War of the Spark|151|C|{2}{G}|Creature - Wolf|3|2|Arlinn's Wolf can't be blocked by creatures with power 2 or less.| +Awakening of Vitu-Ghazi|War of the Spark|152|R|{3}{G}{G}|Instant|||Put nine +1/+1 counters on target land you control. It becomes a legendary 0/0 Elemental creature with haste named Vitu-Ghazi. It's still a land.| Band Together|War of the Spark|153|C|{2}{G}|Instant|||Up to two target creatures you control each deal damage equal to their power to another target creature.| Bloom Hulk|War of the Spark|154|C|{3}{G}|Creature - Plant Elemental|4|4|When Bloom Hulk enters the battlefield, proliferate.| +Bond of Flourishing|War of the Spark|155|U|{1}{G}|Sorcery|||Look at the top three card of your library. You may reveal a permanent card from among them and put it into your hand. Put the rest on the bottom of your library in any order. You gain 3 life.| +Centaur Nurturer|War of the Spark|156|C|{3}{G}|Creature - Centaur Druid|2|4|When Centaur Nurturer enters the battlefield, you gain 3 life.${T}: Add one mana of any color.| +Challenger Troll|War of the Spark|157|U|{4}{G}|Creature - Troll|6|5|Each creature you control with power 4 or greater can't be blocked by more than one creature.| Courage in Crisis|War of the Spark|158|C|{2}{G}|Sorcery|||Put a +1/+1 counter on target creature, then proliferate.| +Evolution Sage|War of the Spark|159|U|{2}{G}|Creature - Elf Druid|3|2|Whenever a land enters the battlefield under your control, proliferate.| +Finale of Devastation|War of the Spark|160|M|{X}{G}{G}|Sorcery|||Search your library and/or graveyard for a creature card with converted mana cost X or less and put it onto the battlefield. If you search your library this way, shuffle it. If X is 10 or more, creatures you control get +X/+X and gain haste until end of turn.| +Forced Landing|War of the Spark|161|C|{1}{G}|Instant|||Put target creature with flying on the bottom of its owner's library.| Giant Growth|War of the Spark|162|C|{G}|Instant|||Target creature gets +3/+3 until end of turn.| +God-Eternal Rhonas|War of the Spark|163|M|{3}{G}{G}|Legendary Creature - Zombie God|5|5|Deathtouch$When God-Eternal Rhonas enters the battlefield, double the power of each other creature you control until end of turn. Those creatures gain vigilance until end of turn.$When God-Eternal Rhonas dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| Jiang Yanggu, Wildcrafter|War of the Spark|164|U|{2}{G}|Legendary Planeswalker - Yanggu|3|Each creature you control with a +1/+1 counter on it has "{T}: Add one mana of any color."$-1: Put a +1/+1 counter on target creature.| +Kraul Stinger|War of the Spark|165|C|{2}{G}|Creature - Insect Assassin|2|2|Deathtouch| +Kronch Wrangler|War of the Spark|166|C|{1}{G}|Creature - Human Warrior|2|1|Trample$Whenever a creature with power 4 or greater enters the battlefield under your control, put a +1/+1 counter on Kronch Wrangler.| Mowu, Loyal Companion|War of the Spark|167|U|{3}{G}|Legendary Creature - Hound|3|3|Trample, vigilance$If one or more +1/+1 counters would be put on Mowu, Loyal Companion, that many plus one +1/+1 counters are put on it instead.| +New Horizons|War of the Spark|168|C|{2}{G}|Enchantment - Aura|||Enchant land$When New Horizons enters the battlefield, put a +1/+1 counter on target creature you control.$Enchanted land has "{T}: Add two mana of any one color."| +Nissa, Who Shakes the World|War of the Spark|169|R|{3}{G}{G}|Legendary Planeswalker - Nissa|5|Whenever you tap a Forest for mana, add an additional {G}.$+1: Put three +1/+1 counters on up to one target noncreature land you control. Untap it. It becomes a 0/0 Elemental creature with vigilance and haste that's still a land.$-8: You get an emblem with "Lands you control have indestructible." Search your library for any number of Forest cards, put them onto the battlefield tapped, then shuffle your library.| +Nissa's Triumph|War of the Spark|170|U|{G}{G}|Sorcery|||Search your library for up to two basic Forest cards. If you control a Nissa planeswalker, instead search your library for up to three land cards. Reveal those cards, put them into your hand, then shuffle your library.| Paradise Druid|War of the Spark|171|U|{1}{G}|Creature - Elf Druid|2|1|Paradise Druid has hexproof as long as it's untapped.${T}: Add one mana of any color.| +Planewide Celebration|War of the Spark|172|R|{5}{G}{G}|Sorcery|||Choose four. You may choose the same mode more than once.$• Create a 2/2 Citizen creature token that's all colors.$• Return target permanent card from your graveyard to your hand.$• Proliferate.$• You gain 4 life.| Pollenbright Druid|War of the Spark|173|C|{1}{G}|Creature - Elf Druid|1|1|When Pollenbright Druid enters the battlefield, choose one —$• Put a +1/+1 counter on target creature.$• Proliferate.| Primordial Wurm|War of the Spark|174|C|{4}{G}{G}|Creature - Wurm|7|6|| +Return to Nature|War of the Spark|175|C|{1}{G}|Instant|||Choose one —$• Destroy target artifact.$• Destroy target enchantment.$• Exile target card from a graveyard.| +Snarespinner|War of the Spark|176|C|{1}{G}|Creature - Spider|1|3|Reach$Whenever Snarespinner blocks a creature with flying, Snarespinner gets +2/+0 until end of turn.| +Steady Aim|War of the Spark|177|C|{1}{G}|Instant|||Untap target creature. It gets +1/+4 and gains reach until end of turn.| +Storm the Citadel|War of the Spark|178|U|{4}{G}|Sorcery|||Until end of turn, creatures you control get +2/+2 and gain "Whenever this creature deals combat damage to a player or planeswalker, destroy target artifact or enchantment defending player controls."| +Thundering Ceratok|War of the Spark|179|C|{4}{G}|Creature - Rhino|4|5|Trample$When Thundering Ceratok enters the battlefield, other creatures you control gain trample until end of turn.| Vivien, Champion of the Wilds|War of the Spark|180|R|{2}{G}|Legendary Planeswalker - Vivien|4|You may cast creature spells as though they had flash.$+1: Until your next turn, up to one target creature gains vigilance and reach.$-2: Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card.| Vivien's Arkbow|War of the Spark|181|R|{1}{G}|Legendary Artifact|||{X}, {T}, Discard a card: Look at the top X cards of your library. You may put a creature card with converted mana cost X or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order.| Vivien's Grizzly|War of the Spark|182|C|{2}{G}|Creature - Bear Spirit|2|3|{3}{G}: Look at the top card of your library. If it's a creature or planeswalker card, you may reveal it and put it into your hand. If you don't put the card into your hand, put it on the bottom of your library.| Wardscale Crocodile|War of the Spark|183|C|{4}{G}|Creature - Crocodile|5|3|Hexproof| -Ajani, the Greathearted|War of the Spark|184|R|{2}{G}{W}|Legendary Planeswalker - Ajani|0|Creatures you control have vigilance.$+1: You gain 3 life.$-2: Put a +1/+1 counter on each creature you control and a loyalty counter on each other planeswalker you control.| +Ajani, the Greathearted|War of the Spark|184|R|{2}{G}{W}|Legendary Planeswalker - Ajani|5|Creatures you control have vigilance.$+1: You gain 3 life.$-2: Put a +1/+1 counter on each creature you control and a loyalty counter on each other planeswalker you control.| Angrath's Rampage|War of the Spark|185|U|{B}{R}|Sorcery|||Choose one —$• Target player sacrifices an artifact.$• Target player sacrifices a creature.$• Target player sacrifices a planeswalker.| +Bioessence Hydra|War of the Spark|186|R|{3}{G}{U}|Creature - Hydra Mutant|4|4|Trample$Bioessence Hydra enters the battlefield with a +1/+1 counter on it for each loyalty counter on planeswalkers you control.$Whenever one or more loyalty counters are put on planeswalkers you control, put that many +1/+1 counters on Bioessence Hydra.| +Casualties of War|War of the Spark|187|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.| Cruel Celebrant|War of the Spark|188|U|{W}{B}|Creature - Vampire|1|2|Whenever Cruel Celebrant or another creature or planeswalker you control dies, each opponent loses 1 life and you gain 1 life.| Deathsprout|War of the Spark|189|U|{1}{B}{B}{G}|Instant|||Destroy target creature. Search your library for a basic land card, put it onto the battlefield tapped, then shuffle your library.| +Despark|War of the Spark|190|U|{W}{B}|Instant|||Exile target permanent with converted mana cost 4 or greater.| +Domri, Anarch of Bolas|War of the Spark|191|R|{1}{R}{G}|Legendary Planeswalker - Domri|3|Creatures you control get +1/+0.$+1: Add {R} or {G}. Creature spells you cast this turn can't be countered.$-2: Target creature you control fights target creature you don't control.| +Domri's Ambush|War of the Spark|192|U|{R}{G}|Sorcery|||Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control.| Dovin's Veto|War of the Spark|193|U|{W}{U}|Instant|||This spell can't be countered.$Counter target noncreature spell.| Dreadhorde Butcher|War of the Spark|194|R|{B}{R}|Creature - Zombie Warrior|1|1|Haste$Whenever Dreadhorde Butcher deals combat damage to a player or planeswalker, put a +1/+1 counter on Dreadhorde Butcher.$When Dreadhorde Butcher dies, it deals damage equal to its power to any target.| +Elite Guardmage|War of the Spark|195|U|{2}{W}{U}|Creature - Human Wizard|2|3|Flying$When Elite Guardmage enters the battlefield, you gain 3 life and draw a card.| +Enter the God-Eternals|War of the Spark|196|R|{2}{U}{U}{B}|Sorcery|||Enter the God-Eternals deals 4 damage to target creature and you gain life equal to the damage dealt this way. Target player puts the top four cards of their library into their graveyard. Amass 4.| +Feather, the Redeemed|War of the Spark|197|R|{R}{W}{W}|Legendary Creature - Angel|3|4|Flying$Whenever you cast an instant or sorcery spell that targets a creature you control, exile that card instead of putting it into your graveyard as it resolves. If you do, return it to your hand at the beginning of the next end step.| Gleaming Overseer|War of the Spark|198|U|{1}{U}{B}|Creature - Zombie Wizard|1|4|When Gleaming Overseer enters the battlefield, amass 1.$Zombie tokens you control have hexproof and menace.| +Heartwarming Redemption|War of the Spark|199|U|{2}{R}{W}|Instant|||Discard all the cards in your hand, then draw that many cards plus one. You gain life equal to the number of cards in your hand.| +Huatli's Raptor|War of the Spark|200|U|{G}{W}|Creature - Dinosaur|2|3|Vigilance$When Huatli's Raptor enters the battlefield, proliferate.| Invade the City|War of the Spark|201|U|{1}{U}{R}|Sorcery|||Amass X, where X is the number of instant and sorcery cards in your graveyard.| Leyline Prowler|War of the Spark|202|U|{1}{B}{G}|Creature - Nightmare Beast|2|3|Deathtouch, lifelink${T}: Add one mana of any color.| +Living Twister|War of the Spark|203|R|{R}{R}{G}|Creature - Elemental|2|5|{1}{R}, Discard a land card: Living Twister deals 2 damage to any target.${G}: Return a tapped land you control to its owner's hand.| Mayhem Devil|War of the Spark|204|U|{1}{B}{R}|Creature - Devil|3|3|Whenever a player sacrifices a permanent, Mayhem Devil deals 1 damage to any target.| Merfolk Skydiver|War of the Spark|205|U|{G}{U}|Creature - Merfolk Mutant|1|1|Flying$When Merfolk Skydiver enters the battlefield, put a +1/+1 counter on target creature you control.${3}{G}{U}: Proliferate.| Neoform|War of the Spark|206|U|{G}{U}|Sorcery|||As an additional cost to cast this spell, sacrifice a creature.$Search your library for a creature card with converted mana cost equal to 1 plus the sacrificed creature's converted mana cost, put that card onto the battlefield with an additional +1/+1 counter on it, then shuffle your library.| +Nicol Bolas, Dragon-God|War of the Spark|207|M|{U}{B}{B}{B}{R}|Legendary Planeswalker - Bolas|4|Nicol Bolas, Dragon-God has all loyalty abilities of all other planeswalkers on the battlefield.$+1: You draw a card. Each opponent exiles a card from their hand or a permanent they control.$-3: Destroy target creature or planeswalker.$-8: Each opponent who doesn't control a legendary creature or planeswalker loses the game.| +Niv-Mizzet Reborn|War of the Spark|208|M|{W}{U}{B}{R}{G}|Legendary Creature - Dragon Avatar|6|6|Flying$When Niv-Mizzet Reborn enters the battlefield, reveal the top ten cards of your library. For each color pair, choose a card that's exactly those colors from among them. Put the chosen cards into your hand and the rest on the bottom of your library in a random order.| +Oath of Kaya|War of the Spark|209|R|{1}{W}{B}|Legendary Enchantment|||When Oath of Kaya enters the battlefield, it deals 3 damage to any target and you gain 3 life.$Whenever an opponent attacks a planeswalker you control with one or more creatures, Oath of Kaya deals 2 damage to that player and you gain 2 life.| +Pledge of Unity|War of the Spark|210|U|{1}{G}{W}|Instant|||Put a +1/+1 counter on each creature you control. You gain 1 life for each creature you control.| +Ral, Storm Conduit|War of the Spark|211|R|{2}{U}{R}|Legendary Planeswalker - Ral|4|Whenever you cast or copy an instant or sorcery spell, Ral, Storm Conduit deals 1 damage to target opponent or planeswalker.$+2: Scry 1.$-2: When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.| +Ral's Outburst|War of the Spark|212|U|{2}{U}{R}|Instant|||Ral's Outburst deals 3 damage to any target. Look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.| +Roalesk, Apex Hybrid|War of the Spark|213|M|{2}{G}{G}{U}|Legendary Creature - Human Mutant|4|5|Flying, trample$When Roalesk, Apex Hybrid enters the battlefield, put two +1/+1 counters on another target creature you control.$When Roalesk dies, proliferate, then proliferate again.| Role Reversal|War of the Spark|214|R|{U}{U}{R}|Sorcery|||Exchange control of two target permanents that share a permanent type.| +Rubblebelt Rioters|War of the Spark|215|U|{1}{R}{G}|Creature - Human Berserker|0|4|Haste$Whenever Rubblebelt Rioters attacks, it gets +X/+0 until end of turn, where X is the greatest power among creatures you control.| +Solar Blaze|War of the Spark|216|R|{2}{R}{W}|Sorcery|||Each creature deals damage to itself equal to its power.| Sorin, Vengeful Bloodlord|War of the Spark|217|R|{2}{W}{B}|Legendary Planeswalker - Sorin|4|As long as it's your turn, creatures and planeswalkers you control have lifelink.$+2: Sorin, Vengeful Bloodlord deals 1 damage to target player or planeswalker.$-X: Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types.| +Soul Diviner|War of the Spark|218|R|{U}{B}|Creature - Zombie Wizard|2|3|{T}, Remove a counter from an artifact, creature, land, or planeswalker you control: Draw a card.| Storrev, Devkarin Lich|War of the Spark|219|R|{1}{B}{B}{G}|Legendary Creature - Zombie Elf Wizard|5|4|Trample$Whenever Storrev, Devkarin Lich deals combat damage to a player or planeswalker, return to your hand target creature or planeswalker card in your graveyard that wasn't put there this combat.| +Tamiyo, Collector of Tales|War of the Spark|220|R|{2}{G}{U}|Legendary Planeswalker - Tamiyo|5|Spells and abilities your opponents control can't cause you to discard cards or sacrifice permanents.$+1: Choose a nonland card name, then reveal the top four cards of your library. Put all cards with the chosen name from among them into your hand and the rest into your graveyard.$-3: Return target card from your graveyard to your hand.| Teferi, Time Raveler|War of the Spark|221|R|{1}{W}{U}|Legendary Planeswalker - Teferi|4|Each opponent can cast spells only any time they could cast a sorcery.$+1: Until your next turn, you may cast sorcery spells as though they had flash.$-3: Return up to one target artifact, creature, or enchantment to its owner's hand. Draw a card.| Tenth District Legionnaire|War of the Spark|222|U|{R}{W}|Creature - Human Soldier|2|2|Haste$Whenever you cast a spell that targets Tenth District Legionnaire, put a +1/+1 counter on Tenth District Legionnaire, then scry 1.| Time Wipe|War of the Spark|223|R|{2}{W}{W}{U}|Sorcery|||Return a creature you control to its owner's hand, then destroy all creatures.| Tolsimir, Friend to Wolves|War of the Spark|224|R|{2}{G}{G}{W}|Legendary Creature - Elf Scout|3|3|When Tolsimir, Friend to Wolves enters the battlefield, create Voja, Friend to Elves, a legendary 3/3 green and white Wolf creature token.$Whenever a Wolf enters the battlefield under your control, you gain 3 life and that creature fights up to one target creature an opponent controls.| +Tyrant's Scorn|War of the Spark|225|U|{U}{B}|Instant|||Choose one —$• Destroy target creature with converted mana cost 3 or less.$• Return target creature to its owner's hand.| Widespread Brutality|War of the Spark|226|R|{1}{B}{R}{R}|Sorcery|||Amass 2, then the Army you amassed deals damage equal to its power to each non-Army creature.| Angrath, Captain of Chaos|War of the Spark|227|U|{2}{B/R}{B/R}|Legendary Planeswalker - Angrath|5|Creatures you control have menace.$-2: Amass 2.| +Ashiok, Dream Render|War of the Spark|228|U|{1}{U/B}{U/B}|Legendary Planeswalker - Ashiok|5|Spells and abilities your opponents control can't cause their controller to search their library.$-1: Target player puts the top four cards of their library into their graveyard. Then exile each opponent's graveyard.| +Dovin, Hand of Control|War of the Spark|229|U|{2}{W/U}|Legendary Planeswalker - Dovin|5|Artifact, instant, and sorcery spells your opponents cast cost {1} more to cast.$-1: Until your next turn, prevent all damage that would be dealt to and dealt by target permanent an opponent controls.| +Huatli, the Sun's Heart|War of the Spark|230|U|{2}{G/W}|Legendary Planeswalker - Huatli|7|Each creature you control assigns combat damage equal to its toughness rather than its power.$-3: You gain life equal to the greatest toughness among creatures you control.| Kaya, Bane of the Dead|War of the Spark|231|U|{3}{W/B}{W/B}{W/B}|Legendary Planeswalker - Kaya|7|Your opponents and permanents your opponents control with hexproof can be the target of spells and abilities you control as though they didn't have hexproof.$-3: Exile target creature.| Kiora, Behemoth Beckoner|War of the Spark|232|U|{2}{G/U}|Legendary Planeswalker - Kiora|7|Whenever a creature with power 4 or greater enters the battlefield under your control, draw a card.$-1: Untap target permanent.| Nahiri, Storm of Stone|War of the Spark|233|U|{2}{R/W}{R/W}|Legendary Planeswalker - Nahiri|6|As long as it's your turn, creatures you control have first strike and equip abilities you activate cost {1} less to activate.$-X: Nahiri, Storm of Stone deals X damage to target tapped creature.| +Saheeli, Sublime Artificer|War of the Spark|234|U|{1}{U/R}{U/R}|Legendary Planeswalker - Saheeli|5|Whenever you cast a noncreature spell, create a 1/1 colorless Servo artifact creature token.$-2: Target artifact you control becomes a copy of another target artifact or creature you control until end of turn, except it's an artifact in addition to its other types.| Samut, Tyrant Smasher|War of the Spark|235|U|{2}{R/G}{R/G}|Legendary Planeswalker - Samut|5|Creatures you control have haste.$-1: Target creature gets +2/+1 and gains haste until end of turn. Scry 1.| Vraska, Swarm's Eminence|War of the Spark|236|U|{2}{B/G}{B/G}|Legendary Planeswalker - Vraska|5|Whenever a creature you control with deathtouch deals damage to a player or planeswalker, put a +1/+1 counter on that creature.$-2: Create a 1/1 black Assassin creature token with deathtouch and "Whenever this creature deals damage to a planeswalker, destroy that planeswalker."| +Firemind Vessel|War of the Spark|237|U|{4}|Artifact|||Firemind Vessel enters the battlefield tapped.${T}: Add two mana of different colors.| God-Pharaoh's Statue|War of the Spark|238|U|{6}|Legendary Artifact|||Spells your opponents cast cost {2} more to cast.$At the beginning of your end step, each opponent loses 1 life.| +Guild Globe|War of the Spark|239|C|{2}|Artifact|||When Guild Globe enters the battlefield, draw a card.${2}, {T}, Sacrifice Guild Globe: Add two mana of different colors.| Iron Bully|War of the Spark|240|C|{3}|Artifact Creature - Golem|1|1|Menace$When Iron Bully enters the battlefield, put a +1/+1 counter on target creature.| +Mana Geode|War of the Spark|241|C|{3}|Artifact|||When Mana Geode enters the battlefield, scry 1.${T}: Add one mana of any color.| +Prismite|War of the Spark|242|C|{2}|Artifact Creature - Golem|2|1|{2}: Add one mana of any color.| +Saheeli's Silverwing|War of the Spark|243|C|{4}|Artifact Creature - Drake|2|3|Flying$When Saheeli's Silverwing enters the battlefield, look at the top card of target opponent's library.| +Blast Zone|War of the Spark|244|R||Land|||Blast Zone enters the battlefield with a charge counter on it.${T}: Add {C}.${X}{X}, {T}: Put X charge counters on Blast Zone.${3}, {T}, Sacrifice Blast Zone: Destroy each nonland permanent with converted mana cost equal to the number of charge counters on Blast Zone.| Emergence Zone|War of the Spark|245|U||Land|||{T}: Add {C}.${1}, {T}, Sacrifice Emergence Zone: You may cast spells this turn as though they had flash.| +Gateway Plaza|War of the Spark|246|C||Land - Gate|||Gateway Plaza enters the battlefield tapped.$When Gateway Plaza enters the battlefield, sacrifice it unless you pay {1}.${T}: Add one mana of any color.| Interplanar Beacon|War of the Spark|247|U||Land|||Whenever you cast a planeswalker spell, you gain 1 life.${T}: Add {C}.${1}, {T}: Add two mana of different colors. Spend this mana only to cast planeswalker spells.| Karn's Bastion|War of the Spark|248|R||Land|||{T}: Add {C}.${4}, {T}: Proliferate.| Mobilized District|War of the Spark|249|R||Land|||{T}: Add {C}.${4}: Mobilized District becomes a 3/3 Citizen creature with vigilance until end of turn. It's still a land. This ability costs {1} less to activate for each legendary creature and planeswalker you control.| @@ -34975,4 +35105,14 @@ Island|War of the Spark|254|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|War of the Spark|258|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|War of the Spark|261|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|War of the Spark|264|C||Basic Land - Forest|||({T}: Add {G}.)| +Gideon, the Oathsworn|War of the Spark|265|M|{4}{W}{W}|Legendary Planeswalker - Gideon|4|Whenever you attack with two or more non-Gideon creatures, put a +1/+1 counter on each of those creatures.$+2: Until end of turn, Gideon, the Oathsworn becomes a 5/5 white Soldier creature that's still a planeswalker. Prevent all damage that would be dealt to him this turn.$-9: Exile Gideon, the Oathsworn and each creature your opponents control.| +Desperate Lunge|War of the Spark|266|C|{1}{W}|Instant|||Target creature gets +2/+2 and gains flying until end of turn. You gain 2 life.| +Gideon's Battle Cry|War of the Spark|267|R|{2}{W}{W}|Sorcery|||Put a +1/+1 counter on each creature you control. You may search your library and/or graveyard for a card named Gideon, the Oathsworn, reveal it, and put it into your hand. If you search your library this way, shuffle it.| +Gideon's Company|War of the Spark|268|U|{3}{W}|Creature - Human Soldier|3|3|Whenever you gain life, put two +1/+1 counters on Gideon's Company.${3}{W}: Put a loyalty counter on target Gideon planeswalker.| +Orzhov Guildgate|War of the Spark|269|C||Land - Gate|||Orzhov Guildgate enters the battlefield tapped.${T}: Add {W} or {B}.| +Jace, Arcane Strategist|War of the Spark|270|M|{4}{U}{U}|Legendary Planeswalker - Jace|4|Whenever you draw your second card each turn, put a +1/+1 counter on target creature you control.$+1: Draw a card.$-7: Creatures you control can't be blocked this turn.| +Guildpact Informant|War of the Spark|271|C|{2}{U}|Creature - Faerie Rogue|1|1|Flying$Whenever Guildpact Informant deals combat damage to a player or planeswalker, proliferate.| +Jace's Projection|War of the Spark|272|U|{2}{U}{U}|Creature - Wizard Illusion|2|2|Whenever you draw a card, put a +1/+1 counter on Jace's Projection.${3}{U}: Put a loyalty counter on target Jace planeswalker.| +Jace's Ruse|War of the Spark|273|R|{3}{U}{U}|Sorcery|||Return up to two target creatures to their owner's hand. You may search your library and/or graveyard for a card named Jace, Arcane Strategist, reveal it, and put it into your hand. If you search your library this way, shuffle it.| +Simic Guildgate|War of the Spark|274|C||Land - Gate|||Simic Guildgate enters the battlefield tapped.${T}: Add {G} or {U}.| Tezzeret, Master of the Bridge|War of the Spark|275|M|{4}{U}{B}|Legendary Planeswalker - Tezzeret|5|Creature and planeswalker spells you cast have affinity for artifacts.$+2: Tezzeret, Master of the Bridge deals X damage to each opponent, where X is the number of artifacts you control. You gain X life.$-3: Return target artifact card from your graveyard to your hand.$-8: Exile the top ten cards of your library. Put all artifact cards from among them onto the battlefield.| \ No newline at end of file diff --git a/pom.xml b/pom.xml index f3118415c4..97942f168f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.34 + 1.4.35 pom Mage Root Mage Root POM @@ -87,7 +87,7 @@ - 1.4.34 + 1.4.35 UTF-8 yyyy-MM-dd'T'HH:mm:ss'Z'